En este artículo vamos a examinar cómo utilizar FastReport Online Designer en una aplicación de única página basada en Angular framework y ASP .Net Core. Por el hecho de que el motor de esta aplicación de única página se implementa en ASP .Net Core, podemos utilizar sin ningún problema las bibliotecas de FastReport. La única pregunta es cómo mostrar el objeto de un informe web en la aplicación de cliente Angular.
A lo major sepa que el framework Angular en su primera versión se implementó en JavaScript. Todas las versions posteriores están escritas en TypeScript. Hoy la primera version de Angular se llama AngularJS, mientras que la segunda tiene un índice digital: 2, 3, ... 7. Vamos a crear una aplicación demo basada en Angular 7.
Antes de empezar a desarrollar, hay que preparar el entorno. Tiene que instalar la plataforma Node js . Esto activará JavaScript en el lado del servidor. Descargue la distribución del sitio web de desarrollador https://nodejs.org/en/ y instálela. Node js incluye el administrador de paquetes NPM que permítenos instalar las bibliotecas pertinentes escritas en JavaScript utilizando consola de comandos. Además, necesita tener instalada la versión 2.0 de .Net Core SDK o o más reciente.
Para crear rápido una nueva aplicación demo, utilice la línea de comandos de Windows. Inicie cmd y pase al directorio en el que quiere crear el proyecto. Ejecute el comando:
dotnet new angular -o AngularOnlineDesignerFRCore
A continuación necesitamos un diseñador en línea. Se descarga desde la sección del cliente de www.fast-report.com. Permítame recordarle que primero tiene que hacer una nueva compilación de un diseñador en línea en el diseñador para el framework .Net Core.
Abra nuestro proyecto en Visual Studio. Después de descargar el archivo con el diseñador en línea, descomprímelo en la carpeta wwwroot del proyecto.
Ahora vamos a añadir las bibliotecas de FastReport al proyecto utilizando el administrador de paquetes NuGet. En la esquina superior derecha del administrador hay una lista despegable de fuentes de paquetes. Necesitamos una fuente local. Pero tiene que ajustarla. Para ello, haga clic en el icono de rueda dentada que está al lado. A continuación seleccione la fuente local y especifíquele la ruta al directorio en el disco local:
C: \ Program Files (x86) \ FastReports \ FastReport.Net \ Nugets. Después de acabar con los ajustes, instale dos paquetes desponibles: FastReport.Core y FastReport.Web.
Para utilizar FastReport en su proyecto también necesita añadir la siguiente línea al archivo Sturtup.cs, en el método especificado:
1 2 3 4 5 6 |
public void Configure(IApplicationBuilder app, IHostingEnvironment env) { … app.UseFastReport(); … } |
Para cargar informes al diseñador, tenemos que añadir los modelos de informespertinentes al servidor. Para ello, creee la carpeta App_Data en la raíz del proyecto. Añada un par de modelos de informes desde la distribución de FastReport.Net que está en la carpeta Demos / Reports. Y copie el archivo de datos desde esta carpeta:
Ahora podemos empezar a programar. Tenemos un controlador, SampleDataController. Como hemos creado una aplicación demo con páginas de modelo, tenemos elementos innecesarios en el controlador y en el cliente. Vamos a eliminar todos los métodos del controlador SampleDataController.cs y añadir nuestro propio método.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 |
using System; using Microsoft.AspNetCore.Mvc; using FastReport.Web; using System.IO; namespace AngularOnlineDesignerFRCore.Controllers { [Route("api/[controller]")] public class SampleDataController : Controller { [HttpGet("[action]")] public IActionResult Design(string report) { WebReport WebReport = new WebReport(); WebReport.Width = "1000"; WebReport.Height = "1000"; WebReport.Report.Load("App_Data/"+report+".frx"); // Load the report into the WebReport object System.Data.DataSet dataSet = new System.Data.DataSet(); // Create a data source dataSet.ReadXml("App_Data/nwind.xml"); // Open the xml database WebReport.Report.RegisterData(dataSet, "NorthWind"); // Registering the data source in the report WebReport.Mode = WebReportMode.Designer; // Set the web report object mode - designer display WebReport.DesignerLocale = "en"; WebReport.DesignerPath = @"WebReportDesigner/index.html"; // We set the URL of the online designer WebReport.DesignerSaveCallBack = @"api/SampleData/SaveDesignedReport"; // Set the view URL for the report save method WebReport.Debug = true; ViewBag.WebReport = WebReport; // pass the report to View return View(); } [HttpPost("[action]")] // call-back for save the designed report public IActionResult SaveDesignedReport(string reportID, string reportUUID) { ViewBag.Message = String.Format("Confirmed {0} {1}", reportID, reportUUID); // We set the message for representation Stream reportForSave = Request.Body; // Write the result of the Post request to the stream. string pathToSave = @"App_Data/TestReport.frx"; // get the path to save the file using (FileStream file = new FileStream(pathToSave, FileMode.Create)) // Create a file stream { reportForSave.CopyTo(file); // Save query result to file } return View(); } } } |
El primer método Design acepta el parametro report que es el nombre del informe para cargar. Creamos el objeto del informe, cargamos el modelo de informe a este y conectramos a la fuente de datos. A continuación, ponga el modo “desarrollar” para el objeto del informe, especifique la ruta a la página del diseñador en línea y la ruta al método de guardar informe.
El segundo método es un callback para guardar informe. Al hacer clic en el botón Save en el diseñador, hamos iniciado un evento de guardar que va a provocar este callback. En este método hemos implementado el guardar del archivo del informe en el servidor bajo el nombre TestReport.
Para estos métodos tiene que crear vistas. Pero en nuestro proyecto hay carpeta Views. Vamos a crearla en el raíz del proyecto. En ella necesita crear otra carpeta, SampleData. Aquí añadimos vistas. Primero, para el método Design. El archivo tiene que llevar el mismo nombre y su contenido es muy lacónico.
1 |
@await ViewBag.WebReport.Render();
|
1 |
@ ViewBag.Message
|
Esta vista muestra un mensaje de estatus para el evento de guardar informe.
En esta etapa la parte de la programación en el lado del servidor se puede considerar hecha. Ahora vamos a la interfaz.
Todo lo que está relacionado con aplicación de única página está en el directorio ClientApp . Expándalo en el navegador de solución. Aquí nos interesa la carpeta src y luego la app.
La función de mostrar la página principal desempeña el componente app.component.ts. Vamos a corregirlo:
1 2 3 4 5 6 7 8 9 10 |
import { Component } from '@angular/core'; @Component({ selector: 'app-root', templateUrl: './app.component.html', styleUrls: ['./app.component.css'], }) export class AppComponent { } |
Pero primeo, fíjese en el modelo de página en el archivo app.component.html:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
<div class='container-fluid'> <div class='row'> <div> <input type="radio" id="reportChoice1" name="report" value="Text" (click)="this.report = 'Text'"> <label for="reportChoice1">Text</label> <input type="radio" id="reportChoice2" name="report" value="Master-Detail" (click)="this.report = 'Master-Detail'"> <label for="reportChoice2">Master-Detail</label> </div> <div> <input type="button" (click)="Clicked()" value="Show Online Designer" /> <div *ngIf="flag" [innerHTML]="html | safeHtml"></div> </div> </div> </div> |
La primera cosa que se nota son los botones de opción. Con estos botones vamos a seleccionar uno de los dos informes que se tiene que abrir en el diseñador en línea. Los botones de opción están suscritos al evento (click). Este evento establece el valor de la variable report. Hablaremos de esta variable cuando terminemos trabajar en el componente de la aplicación.
Aquí está el botón que también está escrito al evento (click). La función Clicked () se invoca con este evento. El siguiente div tiene una condición: si variable flag es true, muestre el código anidado HTML que cogemos desde la variable HTML. Pero preste atención a la función safeHtml que hemos aplicado a la variable HTML a través de la tubería. Esta función normalize código HTML haciéndolo seguro y adecuado para incorporarlo en DOM.
Tenemos que implementar esta función. Para ello, cree un nuevo archivo typescript en esta carpeta (app). Lo llamamos safeHtml.pipe.ts.
1 2 3 4 5 6 7 8 9 10 |
import { PipeTransform, Pipe } from '@angular/core'; import { DomSanitizer } from '@angular/platform-browser'; @Pipe({ name: 'safeHtml' }) export class SafeHtmlPipe implements PipeTransform { constructor(private sanitized: DomSanitizer) { } transform(value) { return this.sanitized.bypassSecurityTrustHtml(value); } } |
La biblioteca DomSanitizer hace todo el trabajo por nosotros y lo único que tenemos que hacer es pasar el código HTML al método bypassSecurityTrustHtml .
Ahora vuelva al componente de la aplicación. Implementamos la función Clicked () en él.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
import { Component } from '@angular/core'; import { HttpClient } from "@angular/common/http"; @Component({ selector: 'app-root', templateUrl: './app.component.html', styleUrls: ['./app.component.css'], providers: [HttpService] }) export class AppComponent { html: string; flag: boolean; report: string; constructor(private http: HttpClient){ } Clicked() { this.flag = false; this.http.get('api/SampleData/Design?report='+report, { headers: { 'Accept': 'text/html' }, responseType: 'text' as 'text' }).).subscribe((data: string) => { this.html = data; this.flag = true }); } } |
Hemos añadido un constructor de clase que acepta HttpClient. Lo necesitamos para realizar la solicitud get. La función Clicked () establece el valor de la variable flag por defecto. A continuación, realize una solicitud get para el método de controlador Design. Como parametro se pasa el nombre del informe desde la variable. Si la solicitud get ha recibido una respuesta positiva, la variable flag se establece como true. Esto va a mostrar el div que el diseñador de informes muestre.
Pero sería bueno que trajéramos una solicitud de un componente a un servidor separado. Vamos a hacerlo. Cree el script http.service.ts en este directorio app:
1 2 3 4 5 6 7 8 9 10 11 |
import { Injectable } from '@angular/core'; import { HttpClient } from '@angular/common/http'; @Injectable() export class HttpService { constructor(private http: HttpClient) { } getData(report) { return this.http.get('api/SampleData/Design?report='+report, { headers: { 'Accept': 'text/html' }, responseType: 'text' as 'text' }); } } |
Ahora vamos a convertir app.component.ts para poder utilizarlo:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
import { Component } from '@angular/core'; import { HttpService } from "./http.service"; @Component({ selector: 'app-root', templateUrl: './app.component.html', styleUrls: ['./app.component.css'], providers: [HttpService] }) export class AppComponent { html: string; flag: boolean; report: string; constructor(private httpService: HttpService) { } Clicked() { this.flag = false; this.httpService.getData(this.report).subscribe((data: string) => { this.html = data; this.flag = true }); } } |
Para que los módulos añadidos se carguen y estén disponibles en el componente, tenemos que añadirlos a app.module.ts:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
import { BrowserModule } from '@angular/platform-browser'; import { NgModule } from '@angular/core'; import { FormsModule } from '@angular/forms'; import { HttpClientModule } from '@angular/common/http'; import { AppComponent } from './app.component'; import { SafeHtmlPipe } from './safeHtml.pipe'; @NgModule({ declarations: [ AppComponent, NavMenuComponent, SafeHtmlPipe ], imports: [ BrowserModule.withServerTransition({ appId: 'ng-cli-universal' }), HttpClientModule, FormsModule ], providers: [], bootstrap: [AppComponent] }) export class AppModule { } |
Ahora tanto el servicio como la tubería se pueden utilizar en AppComponent.
Es suficiente para ejecutar la aplicación y mostrar el diseñador de informes con el informe descargado. Pero hay un detalle que hay que tener en cuenta. Como WebReportDesigner tiene que estar situado en el directorio wwwroot, no puede utilizar la raíz del motor para manipular el evento de guardar informe.
Nos ayudará en esto el proxy del cliente al motor. De esta manera, indicamos que la solicitud se tiene que mandar al puerto del servidor, en nuestro caso es el servidor ng.
Para ajustar el proxy, tenemos que crear el archivo proxy.config.json en el directorio src.
1 2 3 4 5 6 7 |
{ "/": { "target": "http://localhost:4200/", "secure": false, "logLevel": "debug" } } |
En nuestro caso el puerto del servidor ng es 4200 pero puede ser otro. Puede saberlo ejecutanto del servidor desde la consola. Para ello, ejecuta la línea de comandos de Windows, pase al directorio ClientApp utilizando el comando cd y ejecuta: npm start. En el texto que aparece a continuación puede ver el número del puerto deseado.
Ahora abra el archivo package.json en el directorio src y cambie los siguientes ajustes:
1 2 3 4 5 6 7 8 |
{ "scripts": { … "start": "ng serve --proxy-config proxy.config.json", "build": "ng build --prod --output-path ../AngularOnlineDesignerFRCore/wwwroot", … } |
De este modo, hemos especificado el archivo config para el proxy y hemos establecido la carpeta wwwroot para la compilación ClientApp.
Ahora puede ejecutar la aplicación y evaluar el trabajo hecho. Nos espera un página casi vacía, con solo estos elementos de control:
Vamos a seleccionar uno de los dos informes y hacer clic en el botón ShowOnlineDesigner:
Aparece un diseñador con un informe cargado. Haga clic en la pestaña Report y luego en el botón Save :
Si el proxy está ajustado correctamente, verá el mensaje save en el marco verde por la derecha. El archivo del informe se ha guardado en el servidor.
Y con esto terminamos de trabajar en el proyecto de demostración. Vamos a hacer un resumen. El desarrollo del motor es casi el mismo que el desarrollo de una aplicación regular ASP.Net Core. La elaboración definitiva de la interfaz no es tan difícil considerando el hecho de que hemos generado todos los archivos gracias a un solo comando.