Mostrando entradas con la etiqueta MVC. Mostrar todas las entradas
Mostrando entradas con la etiqueta MVC. Mostrar todas las entradas

lunes, 9 de marzo de 2020

Usar SonarQube 8 Community con proyectos .Net Framework

Hola a todos. Esta vez vamos a instalar la espectacular herramienta SonarQube 8 Community edition, para validar proyectos .Net Framework. En mi caso usaré un aplicación MVC 4, escrita en C# usando .Net Framework 4. Esto sirve perfecto para aplicaciones .Net 2.0 o de MVC 5 con Framework 4.5,  tanto escritos en VB.Net o C#. Fue un trabajo de investigación de varios días ya que hay mucha información desactualizada en la Red, dispersa, e incluso en el sitio oficial de SonarQube en GitHub.
SonarQube para los que no saben, permite validar el código, buenas prácticas, temas de seguridad, como top 10 OWASP, bugs, "código que huele mal" o Code Smell y entrega líneas o tips para corregirlo, a veces, hasta con las líneas de código correcta en el lenguaje que estás usando y que se deben usar. Osea, mas en bandeja no se puede. Los resortes quedan online para todos los del team o jefatura, haciendo visible el stastus con colores, tipo semáforo.

Lo primero, recomiendo tener un Servidor Linux, puede ser CentOS o RHEL con Docker instalado, para que tenga el Servidor de SonarQube, de lo contrario en tu mismo Windows 10 Prof/Ent, deberás tener Docker Desktop for Windows  para que entregue el servicio a tu team/usuarios.

Parte 1: Dejar SonarQube corriendo con un usuario no admin


1. Crear Red para que los contenedores de SonarQube y la BBDD Postgres se comuniquen

docker network create redsonarqube

2. Bajar imagen y levantar contenedor de BBDD Postgres SQL

docker run --name sonar-postgres -e POSTGRES_USER=sonar -e POSTGRES_PASSWORD=sonar -d -p 5432:5432 --net redsonarqube postgres

Ejecutar docker ps -a
Para ver como quedó corriendo la BBDD.
3. Bajar imagen y levantar contenedor de SonarQube 8 Community. Quedará corriendo en el port 9000 que es el port por defecto de SonarQube.

docker run --name sonarqube -p 9000:9000 -e SONARQUBE_JDBC_USERNAME=sonar -e SONARQUBE_JDBC_PASSWORD=sonar -e SONARQUBE_JDBC_URL=jdbc:postgresql://sonar-postgres:5432/sonar -d --net redsonarqube sonarqube:8-community

Ejecutar docker ps -a
Para ver como quedó corriendo Sonarqube.

3. Probar. Accede yendo a

http://localhost:9000

Y verás ventana de login:
Si lo dejas en un servidor o tu PC lo quieres dejar con ese servicio, debes darle al resto del team o quien quiera acceder la IP Fija del equipo, por ejemplo:

http://192.168.10.1:9000


4. Logeate a SonarQube con las credenciales.
user: admin
pass: admin

5. Crear un usuario no admin
Usuario: test
pass: 1234

Ve a Administration - Security - Users
En los datos, puedes colocar login, nombre, correo opcional, y clave.
Si entras con ese  usuario test también funciona. 
Ese usuario se lo puedes dar al resto del team.

Parte 2: Configurar .Net para que compile, revise el código y publique en SonarQube


1. Bajar SonarScanner for MSBuild para cada equipo del desarrollador con Visual Studio

https://docs.sonarqube.org/latest/analysis/scan/sonarscanner-for-msbuild/

Bajar el que dice: .NET Framework 4.6+

sonar-scanner-msbuild-4.7.1.2311-net46.zip

Descomprimir en una carpeta dada y cambiarle el nombre por uno más corto, ejemplo:

C:\sonarqube\sonar-msbuild\
2. Configurar en cada equipo desarrollo el uso de SonarQube server

Ir a la carpeta

C:\sonarqube\sonar-msbuild\sonar-scanner-4.1.0.1829\conf

y abrir archivo sonar-scanner.properties

#Configure here general information about the environment, such as SonarQube server connection details for example
#No information about specific project should appear here
#----- Default SonarQube server
sonar.host.url=http://localhost:9000
#----- Default source code encoding
#sonar.sourceEncoding=UTF-8

Esta parte cámbiala por el Servidor de SonarQube
http://192.168.10.1:9000

3. Agregar dos Variables de Ambiente

Ir a las Variables de Entorno, ir a Variables de Sistema (sección inferior) y editar la variable Path, agregar:

- la ruta donde está SonarScanner.MSBuild.exe
  C:\sonarqube\sonar-msbuild

- la ruta de sonar-scanner.bar
  C:\sonarqube\sonar-msbuild\sonar-scanner-4.1.0.1829\bin
(Las rutas de la imagen pueden variar de acuerdo a la guía)

Prueba que quedó ok. Ve a CMD y ve a C:

SonarScanner.MSBuild.exe

(en la documentación de https://github.com/SonarSource/sonar-scanning-examples/tree/master/sonarqube-scanner-msbuild/CSharpProject Dice que se debe usar SonarQube.Scanner.MSBuild.exe pero al correrlo dice que está deprecado en cambio usar SonarScanner.MSBuild.exe)

Presiona Enter y debe decir lo de abajo:
Para probar el Scanner que es el más importante, solo escribe en CMD:

sonar-scanner.bat

Te dirá la versión de SonarQube del servidor. También, tratará de buscar un proyecto para escanear y dará error.
4. Instalar MsBuild 14 o superior en cada equipo de desarrollo

Esto se pide en la documentación oficial: https://docs.sonarqube.org/latest/analysis/scan/sonarscanner-for-msbuild/

Lo primero, es hacer un test rápido. Ve a CMD y escribe

msbuild.exe

Y debe indicar versión 14 o superior. Si te dice una menor como 10 u 12, sigue leyendo.
- Instalar Microsoft .NET Framework 4.6.2
https://www.microsoft.com/es-cl/download/details.aspx?id=48159

Instalar Visual Studio 2015 Edición Community con Update 3
https://my.visualstudio.com/Downloads?q=Visual%20Studio%202015%20with%20Update%203

Este software instala el MsBuild 14 que es la necesaria por SonarQube.

Valida que tengas el archivo (se crea al instalar el IDE recién indicado):

C:\Program Files (x86)\MSBuild\Microsoft\VisualStudio\v14.0\WebApplications\ Microsoft.WebApplication.targets

Agregar a las variables de ambiente de sistema, variable Path, la ruta de MsBuild.exe

C:\Program Files (x86)\MSBuild\14.0\Bin

Probar de nuevo yendo a DOS y escribiendo

msbuild.exe

Y ahora si debe indicar versión 14.

5. Configurar reglas de validación a excluir/ignore
Ir a Administración – Configuration – Analysis Scope

Ir a Sección Ignore Issues on MultipleCriteria y setear dos ítems:

- Rule Key Pattern

Es la regla que queremos ignorar. Por ejemplo, si miramos un bug y vemos el detalle, puede tener esto:

A: Error dice: CSS properties should be valid, y tiene el código css:S4654.

Otro caso que vamos a quitar es
B: Images tags and buttons should have an "alt" attribute
Tiene el código: Web:ImgWithoutAltCheck

Si queremos omitir ambos casos, debemos poner ambos códigos.

- File Path Pattern

Es el patrón regexp de carpetas o archivos que aplicará ese ignore. Ejemplo si queremos aplicarlo sobre todos los CSHTML:

**/Ejemplo.Web/**/*.cshtml

Luego clic en Save.
Nota: si ya tienes un proyecto y quieres volver a procesarlo con este ignore, debes volver a ejecutar el Build al menos (y no Rebuild completo).

6. Ejecutando Bat con Etapas SonarQube

Coloca la solución .Net en un sitio fácil acceso, ejemplo:

C:\SLN\
Donde dentro de SLN\ está el sln de su solución .Net.

Ejemplo
C:\SLN\Ejemplo.Web\   ---> CSHTML, JS, CSS, etc
C:\SLN\mi.sln

Crea un archivo .bat EjecutaSonarQube.bat con esto:

CD "C:\SLN\"

SonarScanner.MSBuild.exe begin /v:"1.0" /n:"MiProyecto" /k:"RELEASE_1" /d:sonar.host.url="http://192.168.10.1:9000" /d:sonar.verbose=false /d:sonar.login="test" /d:sonar.password="1234" /d:sonar.exclusions="**/Ejemplo.Web/**/jquery.unobtrusive-ajax.js, **/Ejemplo.Web/**/MicrosoftMvcAjax.debug.js, **/Ejemplo.Web/Properties/PublishProfiles/**/*, **/Ejemplo.Web/Scripts/MicrosoftAjax.debug.js"

MSBuild.exe "C:\SLN\mi.sln" /t:Build


SonarScanner.MSBuild.exe end /d:sonar.login="test" /d:sonar.password="1234"

Expliquemos un poco. SonarQube tiene 3 etapas:

- Begin

Es el seteo de parámetros de SonarQube. Va el usuario, clave y Host del Servidor de SonarQube, carpetas o archivos excluidos:

/n: Nombre del proyecto

/k: key única, permite navegar directo, ejemplo, si mi key es test_123 permite navegar directo

http://192.168.10.1:9000/dashboard?id=test_123

/v: versión, debería subir en cada Build o Rebuild

Ejemplo: /v:1.0

/d:sonar.verbose: true o false. Agrega más información tanto en análisis del cliente o del servidor.

/d:sonar.exclusions: archivos o carpetas a excluir. En mi caso sacaré archivos js de Microsoft que no quiero analizar ya que producen falsos positivos. Si agregas más de uno, sepáralos por coma.

Más parámetros acá: https://docs.sonarqube.org/7.4/analysis/analysis-parameters/

En el .Bat se ve acá esta etapa:
SonarScanner.MSBuild.exe begin /v:"1.0" /n:"MiProyecto" /k:"RELEASE_1" /d:sonar.host.url="http://192.168.10.1:9000" /d:sonar.verbose=false /d:sonar.login="test" /d:sonar.password="1234" /d:sonar.exclusions="**/Ejemplo.Web/**/jquery.unobtrusive-ajax.js, **/Ejemplo.Web/**/MicrosoftMvcAjax.debug.js, **/Ejemplo.Web/Properties/PublishProfiles/**/*, **/Ejemplo.Web/Scripts/MicrosoftAjax.debug.js"

- Etapa de Build o Rebuild

En el bat se ve en esta sección:
MSBuild.exe “C:\SLN\mi.sln” /t:Rebuild

Si ha has hecho Rebuild varias veces y no quieres hacer todo el proceso de nuevo usas “Build”:
MSBuild.exe “C:\SLN\mi.sln” /t:Build

- Etapa End

Se coloca el usuario y clave del servidor (usuario no admin) ya que aquí se envía la información al server . En el bat se ve en esta sección.

SonarScanner.MSBuild.exe end /d:sonar.login="test" /d:sonar.password="1234"
Ejecutando el Bat
Un truco simple, es abrir CMD y arrastar y solar el bat
Luego presionar Enter.

Yo recomiendo tener dos .bat

   EjecutaSonarQubeBuild.bat   -- ejecuta Build
   EjecutaSonarQubeRebuild.bat  -- ejecuta Rebuild

Así lo ejecutas según el caso.

Al presionar Enter se puede demorar unos 10 minutos si es Rebuild, o 5 minutos si es Build, aunque los tiempos varían según el equipo que corre el build y la calidad de la red (debe subir los informes a SonarQube server local)

Se verá en pantalla todos los pasos y detalle de compilación. Cuando termine mostrará resumen.
Nota: A veces se queda pegado pegado en "More about the report…", presiona Control + C para que termine de procesar y muestre resumen.

7. Validando reportes en SonarQube server

Si entras a SonarQube - Projects, se verán todos los proyectos que se están escaneando
Si haces clic en el proyecto que acabas de subir, se ve el detalle.

Si entras a Vulnerabilities se ve el detalle de los errores de seguridad.
Si haces clic en Bugs se ven los errores (no de seguridad)

Si pinchas en un error dado, se ve el detalle y la línea donde está el error.
Ahora si haces clic en See rule se ve una explicación del error y como corregirlo en ese lenguaje:


Si mandas a compilar de nuevo con el .bat. se genera otro proyecto, ya que la fecha-hora cambió.

8. Dejar los reportes más nuevos
Puedes seleccionar el proyecto de SonarQube Server, con las credenciales de Admin, entrar, Administration , Deletion. Así borras los reportes más viejos.

Bueno, con esto, te dejo a ti el resto, investiga y juegueee.

lunes, 30 de mayo de 2016

Tutorial de uso de la librería Winnovative HTML to PDF con C# y MVC

Hola, hace rato que no publicaba nada...Bueno hoy veremos como usar la librería Winnovative HTML to PDF Converter Library usando C# y MVC .Net. El tema nació en que buscamos una forma buena y elegante de imprimir un PDF a partir de un sitio web hecho en MVC .Net, pudiendo personalizar el PDF, tipo de hoja, orientación, que permita imprimir un sitio que haga request Ajax y tenga imágenes.

Personalmente conozco 3 herramientas que permiten generar PDF desde C# y me quedo con Winnovative por lo anterior dicho:


El tutorial fue probado con IE 11, Chrome 50.0.2661.102, FireFox 37.0.1.

Requisitos


  • Tener la librería Winnovative HTML to PDF Converter Library v12.16, descargarla de http://www.winnovative-software.com/download.aspx. En mi caso bajé Winnovative HTML to PDF Converter Library Optimized for 64-bit ya que tanto los PC de los desarrolladores como servidores son de 64 bit.
  • En mi caso usaré una versión comprada HTML to PDF Converter Redistributable License cuyo valor es de 1200 USD ya que permite instalarlo en N Servidores, incluyendo en PC de desarrolladores y en servidores de clientes. No es obligación tener la licencia para el ejemplo, lo que sí, saldrán tus PDF con la frase Winnovative PDF Tools Demo en la parte inferior:
  • Para el ejemplo usaré Visual Studio 2013 con C#, MVC 4 y Framework 4.0
NOTA: la librería no es compatible con el Framework .Net 3.5, si con las versiones 2.0, 4.0 y 4.5.

Paso a paso


Pasos generales

  • Crear un proyecto MVC 4 usando C# con Framework 4.0.
  • Vamos a instalar la librería Winnovative "a la antigua", es decir manualmente, ya que se puede instalar por NuGet. Descomprimir el archivo descargado WnvHtmlToPdf-v12.16-x64.zip, en la raíz del zip estarán estos tres archivos que son compatibles si usas al Framework 4.0: wnvhtmltopdf.dll, wnvinternal.dat y Help.chm.
  • Si usas el Framework 2.0 debes copiar los 3 archivos de la carpeta NET_2.0 (wnvhtmltopdf.dll, wnvhtmltopdf.xml y wnvinternal.dat) al directorio bin de tu proyecto web. El XML contiene sólo la documentación de los fuentes.
  • Si están con Framework 4.0. debes copiarlos a una nueva carpeta LIB. Preocúpate los archivos que no estén chequeados como "solo lectura".
  • El archivo wnvhtmltopdf.dll dentro de LIB debes referenciarlo desde tu proyecto MVC .NET.
  • Agregar en el HomeController el using:

    //HomeController.cs
    using Winnovative;
  • Si usas la Key, y la agregas en el código, no se verá el mensaje "Winnovative PDF Tools Demo":

    //HomeController.cs
    htmlToPdfConverter.LicenseKey = "keyComprada";

Imprimir a pdf una vista o página externa

  • Crear el vista principal con un botón dentro de un Form que llamará a la impresión, por ejemplo:

    <!-- Index.cshtml -->
    @using (Html.BeginForm("ImprimeVistaExterna", "Home", FormMethod.Post))
    {
    <input type="submit" id="impresion2" name="impresion2" value="imprimir otra vista completa" /gt;
    }
  • Creas el Action y la Vista Externa a ser llamada. Esta vista puede tener imágenes, o un llamado Ajax a otra Vista ¡y todo eso se verá impreso!. Esa es la gracia de la librería ya que mucha no hacen eso, esta ejecuta el request con todo lo que conlleva, llamados Ajax, JS, estilos, imágenes y todo el resultado del HTML final, es el que imprime. En mi caso incluso uso la librería Kendo MVC para mostrar un TreView y esta igual se imprime:

    <!-- VistaExterna.cshtml -->
    @{
    ViewBag.Title = "Impresion ejemplo";
    }

    <h1>Vista demo de impresión</h1>

    @(Html.Kendo().TreeView()
    .Name("LinksTreeView")
    .Items(items =>
    {
    items.Add()
    .Text("Home")
    .Action("Index", "Home");
    items.Add()
    .Text("About")
    .Action("About", "Home");
    items.Add()
    .Text("Contact")
    .Action("Contact", "Home");
    })
    )

    <img src="~/Images/pacman.png" />

    <div id="contenedor"></div>

    <script>
    $(document).ready(function () {
    $.ajax({
    url: "/Home/VistaExternaAjax",
    type: 'GET',
    datatype: 'html',
    success: function (data) {
    $("#contenedor").html(data);
    }
    });
    });
    </script>
  • Por último creamos la vista externa (la más externa) que es llamada desde al Ajax anterior:

    <!-- VistaExternaAjax.cshtml -->
    <h1>Otra Vista externa invocada por ajax</h1> <h2>holaaa</h2>
  • Si abrimos la Vista externa en el navegador se verá así:
  • Creas el Action ImprimeVistaExterna dentro del controlador Home:
    //HomeController.cs
    [HttpPost]
    public ActionResult ImprimeVistaExterna()
    {
    // Create a HTML to PDF converter object with default settings
    HtmlToPdfConverter htmlToPdfConverter = new HtmlToPdfConverter();
    
    // Set HTML Viewer width in pixels which is the equivalent in converter of the browser window width
    htmlToPdfConverter.HtmlViewerWidth = 1024;
                
    htmlToPdfConverter.LicenseKey = "tuKey"; //propiedad opcional
    htmlToPdfConverter.HtmlViewerHeight = 768;
    
    // Set PDF page size which can be a predefined size like A4 or a custom size in points 
    // Leave it not set to have a default A4 PDF page
    htmlToPdfConverter.PdfDocumentOptions.PdfPageSize = PdfPageSize.Letter;
    
    // Set PDF page orientation to Portrait or Landscape
    // Leave it not set to have a default Portrait orientation for PDF page
    htmlToPdfConverter.PdfDocumentOptions.PdfPageOrientation = PdfPageOrientation.Portrait;
    
    // Set the maximum time in seconds to wait for HTML page to be loaded 
    // Leave it not set for a default 60 seconds maximum wait time
    htmlToPdfConverter.NavigationTimeout = 4;
    
    // Set an adddional delay in seconds to wait for JavaScript or AJAX calls after page load completed
    // Set this property to 0 if you don't need to wait for such asynchcronous operations to finish
    htmlToPdfConverter.ConversionDelay = 1;
    
    string url = "http://localhost:50422/Home/VistaExterna";
    
    // Convert the HTML page given by an URL to a PDF document in a memory buffer
    byte[] outPdfBuffer = htmlToPdfConverter.ConvertUrl(url);
    
    // Send the PDF file to browser
    FileResult fileResult = new FileContentResult(outPdfBuffer, "application/pdf");
    fileResult.FileDownloadName = "ImpresionVistaExterna.pdf";
    
    return fileResult;
    }
    
  • Notar que en url defines la ruta que se imprimirá. Se le hará un request a dicha ruta. Puede ser cualquier URL, incluso una externa como www.google.com
  • Con htmlToPdfConverter.NavigationTimeout se define el tiempo máximo de espera a dicho request. Yo usaré 4 segundos.
  • Con fileResult.FileDownloadName le defines el nombre del archivo PDF de salida.
  • Con htmlToPdfConverter.PdfDocumentOptions.PdfPageSize = PdfPageSize.Letter defines el tipo de papel, en este caso tipo Carta.
  • Con htmlToPdfConverter.PdfDocumentOptions.PdfPageOrientation = PdfPageOrientation.Portrait defines la orientación del documento.
  • Al hacer clic en "Imprimir página Externa", se bajará el PDF resultante y su lo abres es el mismo contenido HTML que tiene la vista externa:


Imprimir a pdf una sección de la página actual

  • La sección debe ser una Vista Parcial, así que creas la vista Seccion.cshtml:

    <!-- Seccion.cshtml -->
    @{
    ViewBag.Title = "Seccion ejemplo";
    var nombre = ViewBag.Nombre;
    }

    <div class="panel">
    <div class="panel-body">

    @using (Html.BeginForm("ImpresionSeccionVistaActual", "Home", FormMethod.Post))
    {
    <h3>Sección: @nombre</h3>
    <br />
    <img src="~/Images/pacman.png" />
    }

    <center>
    <div class="rotate">Este texto está rotado 90 grados</div>
    </center> </div> </div>
  • El ejemplo contiene un texto rotado en 90 grados que se verá impreso de igual forma, por lo tanto debes agregar en la sección de estilos el siguiente código:

    <!-- _Layout.cshtml -->
    <style>
    .rotate {
    font-family: Arial, Helvetica, sans-serif;
    font-size: 13px;
    -webkit-transform: rotate(-90deg);
    -moz-transform: rotate(-90deg);
    -o-transform: rotate(-90deg);
    -ms-transform: rotate(-90deg);
    -sand-transform: rotate(90deg);
    }
    </style>
  • Creamos en la vista principal un botón Imprimir que llame al Action que haremos luego. Notar que agregamos dos bloques de texto antes y después para que se vea que solo se imprimirá la vista Seccion.cshtml:

    <!-- Index.cshtml -->
    <h2>Texto anterior</h2>
    @{
    Html.RenderAction("Seccion", "Home", new { nombre = nombre });
    }

    @using (Html.BeginForm("ImpresionSeccionVistaActual", "Home", FormMethod.Post))
    {
    @Html.Hidden("nombre", (object)nombre)
    <input type="submit" id="impresion2" name="impresion2" value="imprimir sólo sección" />
    }
    <h2>Texto posterior</h2>
  • En el HomeController crear el Action ImpresionSeccionVistaActual que procesa la sección:
    //HomeController.cs
    [HttpPost]
    public ActionResult ImpresionSeccionVistaActual(FormCollection collection)
    {
    object model = null;
    ViewDataDictionary viewData = new ViewDataDictionary(model);
    
    // transmit the posted data to view
    viewData.Add("nombre", collection["nombre"]);
    
    // The string writer where to render the HTML code of the view
    StringWriter stringWriter = new StringWriter();
    
    // Render the Index view in a HTML string
    ViewEngineResult viewResult = ViewEngines.Engines.FindView(ControllerContext, "Seccion", null);
    ViewContext viewContext = new ViewContext(
    ControllerContext,
    viewResult.View,
    viewData,
    new TempDataDictionary(),
    stringWriter
    );
    viewResult.View.Render(viewContext, stringWriter);
    
    // Get the view HTML string
    string htmlToConvert = stringWriter.ToString();
    
    // Get the base URL
    String currentPageUrl = this.ControllerContext.HttpContext.Request.Url.AbsoluteUri;
    String baseUrl = currentPageUrl.Substring(0, currentPageUrl.Length - "Home/Seccion".Length);
    
    // Create a HTML to PDF converter object with default settings
    HtmlToPdfConverter htmlToPdfConverter = new HtmlToPdfConverter();
    
    // Set license key received after purchase to use the converter in licensed mode
    // Leave it not set to use the converter in demo mode
    htmlToPdfConverter.LicenseKey = key;
    
    // Set an adddional delay in seconds to wait for JavaScript or AJAX calls after page load completed
    // Set this property to 0 if you don't need to wait for such asynchcronous operations to finish
    htmlToPdfConverter.ConversionDelay = 2;
    
    // Convert the HTML string to a PDF document in a memory buffer
    byte[] outPdfBuffer = htmlToPdfConverter.ConvertHtml(htmlToConvert, baseUrl);
    
    // Send the PDF file to browser
    FileResult fileResult = new FileContentResult(outPdfBuffer, "application/pdf");
    fileResult.FileDownloadName = "ImpresionSeccionVistaActual.pdf";
    
    return fileResult;
    }
    
  • Si haces clic en "Imprimir solo sección", se verá el PDF:
  • La imagen de la aplicación final ejecutándose en el navegador con dos botones, Imprimir Sección o Imprimir página externa, se ve así:

Mostrar PDF en una nueva pestaña del navegador al hacer clic en imprimir (y no en la barra de descargas)


Cambiar la clase Controller:
//HomeController.cs
// Convert the HTML page given by an URL to a PDF document in a memory buffer
byte[] outPdfBuffer = htmlToPdfConverter.ConvertUrl(url);            

// Send the PDF file to browser
//FileResult fileResult = new FileContentResult(outPdfBuffer, "application/pdf");            
//fileResult.FileDownloadName = "ImpresionVistaExterna.pdf";
//return fileResult;

MemoryStream stream = new MemoryStream(outPdfBuffer);
Response.AppendHeader("content-disposition", "inline; filename=file.pdf");
return new FileStreamResult(stream, "application/pdf");
Cambiar la vista para que abra en una nueva pestaña con target _black:

<!-- Index.cshtml -->
@using (Html.BeginForm("ImprimeVistaExterna", "Home", FormMethod.Post, new { target= "_blank" }))
{
<input type="submit" id="impresion2" name="impresion2" value="ver pdf generado directo en otra pestaña" />
}



Mostrar PDF en una nueva pestaña del navegador al hacer clic en imprimir y se abra automáticamente el cuadro de diálogos de las impresoras


Esto es útil si el usuario quiere hacer la menor cantidad posible de clic para imprimir un documento PDF. Se le debe asignar el key (si tienes) de nuevo al Document. Recordar igual lo del target _blank en la vista que tiene el botón.
//HomeController.cs
string url = "http://localhost:50422/Home/VistaExterna";

// Convert the HTML page given by an URL to a PDF document in a memory buffer
byte[] outPdfBuffer = htmlToPdfConverter.ConvertUrl(url);                       

//mergeResultPdfDocument.OpenAction.Action = New PdfActionJavaScript("print()")

// Send the PDF file to browser
//FileResult fileResult = new FileContentResult(outPdfBuffer, "application/pdf");            
//fileResult.FileDownloadName = "ImpresionVistaExterna.pdf";
//return fileResult;

MemoryStream stream = new MemoryStream(outPdfBuffer);

Document document = new Document(stream);
document.LicenseKey = key;
document.OpenAction.Action = new PdfActionJavaScript("print()");
byte[] b = document.Save();
Stream strm = new MemoryStream(b);
           
Response.AppendHeader("content-disposition", "inline; filename=file.pdf");
return new FileStreamResult(strm, "application/pdf");


Links útiles

jueves, 22 de enero de 2015

TreeList con Kendo UI for ASP.NET MVC

Este post va dirigido a los que hemos trabajado con Kendo UI para ASP.NET MVC y no hemos podido hacer un simple TreeList en menos de 1 hora.

Primero, tenía instalada la versión de Kendo 2014.2.1008 y escribía en mi vista:
@(Html.Kendo().TreeList

Y arrojaba error ya que no encontraba el .TreeList, luego viendo lo ejemplo de otras versiones me di cuenta que está incluido en la versión 2014.3.1316 por lo que recomiendo bajar esta. Además que actualmente es la última (por lo que averigüé el TreeList se agregó a Kendo en la versión 2014.3.1119).

La documentación es mucha, de hecho me perdí tanto leer, al menos en Telerik encontré:

http://docs.telerik.com/kendo-ui/api/javascript/ui/treelist
http://docs.telerik.com/kendo-ui/aspnet-mvc/helpers/treelist/overview
http://demos.telerik.com/kendo-ui/treelist/index

Luego tenemos los ejemplos que vienen en el ZIP del Kendo comercial (pagado):

- telerik.ui.for.aspnetmvc.2014.3.1316.commercial.zip, para MVC/ASPX, tiene muchos ejemplos, uno de TreeList que es muy complejo quizá para alguien que quiere hacer su primer TreeList.
- telerik.kendoui.professional.2014.3.1316.commercial.zip, para JQuery, con otros muchos ejemplos pero el código de los controladores se leen de un servicio en línea por ejemplo:
var service = "http://demos.telerik.com/kendo-ui/service";

Luego de ver mucho código, partí de cero y era más simple de lo que pensaba.

Primero tenemos una vista que llama a otra vista Header para la carga de los JS/CSS necesario, como JQuery/Kendo:
<! DOCTYPE html >
<html>
<head>
    @Html.Partial("~/HeaderCssJs.cshtml")
</head>

    

    <div class="demo-section k-header">
        @(Html.Kendo().TreeList<models .miviewmodel="">()
        .Name("treelist")
        .Columns(columns =>
        {
            columns.Add().Field(e => e.FirstName).Width(220).TemplateId("photo-template");
            columns.Add().Field(e => e.LastName).Width(160);
            columns.Add().Field(e => e.Address).Title("Address");            
        })
        .Filterable()
        .Sortable()
        .DataSource(dataSource => dataSource
            .Read(read => read.Action("Read", "Home"))
            .ServerOperation(false)
            .Model(m =>
            {
                m.Id(f => f.EmployeeId);
                m.ParentId(f => f.ReportsTo);
                m.Field(f => f.FirstName);
                m.Field(f => f.LastName);
                m.Field(f => f.ReportsTo);
            })
        )
        .Height(540)
        )
    </div>
<style>
        .employee-photo {
            display: inline-block;
            width: 40px;
            height: 40px;
            border-radius: 50%;
            background-size: 40px 44px;
            background-position: center center;
            vertical-align: middle;
            line-height: 41px;
            box-shadow: inset 0 0 1px #999, inset 0 0 10px rgba(0,0,0,.2);
        }

        .employee-name {
            display: inline-block;
            vertical-align: middle;
            line-height: 41px;
            padding-left: 10px;
        }
    </style>
</body>
En el Controlador tenemos 3 actions.

El que carga inicialmente la Vista:
public ActionResult Index()
{
    return View();
}
El Reader que se llama desde el Evento del TreeList:
public JsonResult Read([DataSourceRequest] DataSourceRequest request)
{
   var result = GetDetalle().ToTreeDataSourceResult(request);           
   return Json(result, JsonRequestBehavior.AllowGet);
}
El que carga el enumerador. Lo mejor es que no importa el orden que se hagan los Add, el Tree List de Kendo se encarga de ordenar los hijos:
private IEnumerable GetDetalle()
{
    List<MiViewModel> lista = new List< MiViewModel >();
    lista.Add(new MiViewModel () { EmployeeId = 9, FirstName = "xxxxx", LastName = "yyy", hasChildren = false, ReportsTo = 3, Address = "999", Color = "rojo" });
    lista.Add(new MiViewModel () { EmployeeId = 1, FirstName = "naldo", LastName = "Gonz", hasChildren = true, ReportsTo = null, Address = "123", Color = "rojo" });
    lista.Add(new MiViewModel () { EmployeeId = 2, FirstName = "pepe", LastName = "Super", hasChildren = false, ReportsTo = 1, Address = "456", Color = "rojo" });
    lista.Add(new MiViewModel () { EmployeeId = 3, FirstName = "juan", LastName = "Man", hasChildren = false, ReportsTo = null, Address = "789", Color = "verde" });
    return from a in lista
           select a;
}
En mi caso tengo una carpeta “img” con dos imágenes “rojo.jpg” y “verde.jpg”.

Por ultimo la clase MiViewModel:
using System.Web;

namespace Models
{
    public class MiViewModel
    {
        public MiViewModel() { }       
        public int? EmployeeId { get; set; }
        public string FirstName { get; set; }
        public string LastName { get; set; }
        public string Pos { get; set; }
        public string Color { get; set; }
        public string Address { get; set; }
        public bool hasChildren { get; set; }        
        public int? ReportsTo { get; set; }       
    }        
}

El resultado es:


Ahora veamos algunas cosillas que podemos agregar. Por ejemplo cambiar el texto que muestra el botón de arriba, en vez de "Add New Record", ponemos "Crear":
@(Html.Kendo().TreeList<Modelo>()
.Toolbar(toolbar => toolbar.Create().Text("Crear"))
Como hacer botones propios en el header del TreeList
En la definición del TreeList:
.Toolbar(toolbar =>
{
   toolbar.Custom().Name("botoncrear").Text("Crear");
   toolbar.Custom().Name("mycommand").Text("View X");
})
Luego en la sección scripts ponemos el handler del botón que lee una propiedad del modelo por ejemplo (en base a la celda seleccionada) y lo muestra en un alert:

Como dejar una fila seleccionable (al hacer clic en una celda toda la fila queda seleccionada):
@(Html.Kendo().TreeList<Modelo>()
  .Selectable(true)
Como obtener dicha fila seleccionada con jQuery:
var treeList = $("#contenedor").data("kendoTreeList");
var row = treeList.select();       
Como cambiarle el estilo a una fila seleccionada, por ejemplo seleccionarla y dejarla con estilo oblique (parecido a Italic):
var treeList = $("#contenedor").data("kendoTreeList");
var row = treeList.select();            
row.css('font-style', 'oblique');

Espero les sirva.