Как использовать Online Designer в одностраничном приложении Angular

28.04.2019

В этой статье мы рассмотрим, как использовать FastReport Online Designer  в одностраничном приложении на основе фреймворка Angular и ASP .NET Core. Благодаря тому, что бэкенд такого SPA приложения реализуется на ASP .NET Core мы можем с легкостью использовать библиотеки FastReport. Остается лишь один вопрос: как отобразить объект веб отчета в клиентском приложении Angular.

Вы наверняка знаете, что фреймворк Angular в первой версии был реализован на JavaScript. Все последующие версии написаны на TypeScript. Сейчас первая версия Angular называется AngularJS, а другие имеют цифровой индекс: 2, 3 .. 7. Мы создадим демонстрационное приложение на основе Angular 7.

Прежде чем приступать к разработке, давайте подготовим окружение. Необходимо установить платформу Node js. Она позволит выполнять JavaScript на стороне сервера. Скачиваем дистрибутив с сайта производителя https://nodejs.org/en/ и устанавливаем. В состав Node js входит менеджер пакетов NPM, который позволит нам устанавливать нужные библиотеки, написанные на JavaScript с помощью консольных команд. А также, у вас должен быть установлен .NET Core SDK 2.0 и выше.

Чтобы быстро создать демонстрационное приложение воспользуемся командной строкой Windows. Запускаем cmd и переходим к каталогу, в котором хотим создать проект. И выполняем команду:

dotnet new angular -o AngularOnlineDesignerFRCore

 Далее, нам понадобится онлайн дизайнер. Скачиваем его в клиентском разделе www.fast-report.com. Напомню, что вам предварительно нужно собрать свежую сборку онлайн дизайнера в конструкторе для фреймворка .NET Core

Откройте наш проект в Visual Studio. Скачав архив с онлайн дизайнером, разархивируйте его в папку wwwroot в проекте.

Теперь добавим библиотеки FastReport в проект с помощью менеджера пакетов NuGet. В правом верхнем углу менеджера есть выпадающий список источников пакетов. Нам нужен локальный источник. Но его нужно настроить. Для этого нажимаем на иконку в виде шестеренки рядом. Далее выбираем локальный источник и задаем для него путь к каталогу на локальном диске:

 С:\Program Files (x86)\FastReports\FastReport.Net\Nugets. После завершения настроек устанавливаем два доступных пакета: FastReport.Core и FastReport.Web.

Чтобы использовать FastReport в своем проекте, требуется также добавить в файл Sturtup.cs, в указанный метод следующую строку:

1
2
3
4
5
6
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
 {

 app.UseFastReport();

}

Для загрузки отчетов в дизайнер, нам необходимо добавить нужные шаблоны отчетов на сервер. Для этого создаем в корне проекта папку App_Data. В него добавим пару шаблонов отчетов из поставки FastReport .NET, папка Demos/Reports. Также, скопируйте из этой папки файл данных nwind.xml:

 

Теперь можно приступить к программированию. В нашем распоряжении есть один контроллер – SampleDataController. Так как мы создали демонстрационное приложение с примерами страниц, в контроллере и в клиенте мы имеем ненужные элементы. Давайте удалим из контроллера SampleDataController.cs все методы и добавим свои.

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
45
46
47
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"); //Загружаем отчет в объект WebReport
 System.Data.DataSet dataSet = new System.Data.DataSet(); //Создаем источник данных
 dataSet.ReadXml("App_Data/nwind.xml"); //Открываем базу данных xml
 WebReport.Report.RegisterData(dataSet, "NorthWind"); //Регистрируем источник данных в отчете
 
 WebReport.Mode = WebReportMode.Designer; //Устанавливаем режим объекта веб отчет - отображение дизайнера
 WebReport.DesignerLocale = "en";
 WebReport.DesignerPath = @"WebReportDesigner/index.html"; //Задаем URL онлайн дизайнера
 WebReport.DesignerSaveCallBack = @"api/SampleData/SaveDesignedReport"; //Задаем URL представления для метода сохранения отчета
 WebReport.Debug = true;
 ViewBag.WebReport = WebReport; //передаем отчет во 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); //Задаем сообщение для представления
 
 Stream reportForSave = Request.Body; //Записываем в поток результат Post запроса
 string pathToSave = @"App_Data/TestReport.frx"; //получаем путь для сохранения файла
 using (FileStream file = new FileStream(pathToSave, FileMode.Create)) //Создаем файловый поток
 {
 reportForSave.CopyTo(file); //Сохраняем результат запроса в файл
 }
 return View();
 }
 }
}

Первый метод Design принимает параметр report, который представляет собой имя отчета для загрузки. Мы создаем объект отчета, загружаем в него шаблон отчета и подключаем источник данных. Далее включаем для объекта отчета режим разработки, задаем путь к странице онлайн дизайнера и путь к методу сохранения отчета.

Второй метод представляет собой обратный вызов для сохранения отчета. Нажав на кнопку Save в дизайнере, мы инициируем событие сохранения, которое вызовет этот call-back. В этом методе мы реализовали сохранение файла отчета на сервере под именем TestReport.

Для этих методов нужно создать представления. Но в нашем проекте отсутствует папка Views. Давайте создадим ее в корне проекта. Внутри нее нужно создать еще одну папку – SampleData. Вот в нее уже добавляем представления. Сначала для метода Design. Файл называется также, а его содержимое очень лаконично:

1
@await ViewBag.WebReport.Render();

Мы просто выводим объект веб отчета. Метод Render преобразует его в html. Добавим еще одно представление, для метода SaveDesignedReport:

1
@ ViewBag.Message

Это представление выводит статусное сообщение для события сохранения отчета.

На этом программирование серверной части можно считать законченным. Переходим к фронтэнду.

Все, что относится к одностраничному приложению располагается в каталоге ClientApp. Развернем его в обозревателе решения. Внутри нас интересует папка src, а затем app.

За отображение главной страницы отвечает компонент app.component.ts. Его нам и предстоит редактировать:

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 {
}

Но сначала, рассмотрим шаблон страницы в файле 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>

Первое, на что вы обратите внимание – радио кнопки. С помощью них мы выберем один из двух отчетов, которые нужно открыть в онлайн дизайнере. Радиокнопки подписаны на событие (click). В этом событии задается значение переменной report. О ней мы поговорим при доработке компонента приложения.

Далее располагается кнопка, которая тоже подписана на событие нажатия. По этомй событию вызывается функция Clicked(). Следующий div имеет условие – если переменная flag равна true, то отобразить вложенный html код, который мы возьмем из переменной html. Но обратите внимание на функцию safeHtml, которую мы применили к переменной html через пайп |. Эта функция нормализует html код, делая его безопасным и пригодным для встраивания в DOM.

Нам предстоит реализовать эту функцию. Для этого создадим новый typescript файл в текущей папке – app. Назовем его 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);
 }
}

Библиотека DomSanitizer делает за нас всю работу, нужно лишь отдать html код методу bypassSecurityTrustHtml.

Вернемся к компоненту приложения. Реализуем в нем функцию Clicked():

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 });
 }
}

Мы добавили конструктор класса, который принимает HttpClient. Он нужен нам для выполнения get запроса. Функция Clicked() задает значение переменной flag по умолчанию. Далее - выполняет get запрос к методу контроллера Design. В качестве параметра передается название отчета из переменной report. Если get запрос получил успешный ответ, то переменная flag устанавливается в true. Тем самым отобразится div, который будет отображать дизайнер отчетов.

Но, хорошим тоном было бы вынести запрос из компонента в отдельный сервис. Давайте так и поступим. Создадим в текущем каталоге app скрипт http.service.ts:

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' });
 }
}

А теперь преобразуем app.component.ts для его использования:

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 });
}
}

 Чтобы добавленные модули загружались и были доступны в компоненте их нужно добавить в 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 { }

Теперь и сервис и пайп можно использовать в AppComponent.

Этого достаточно, чтобы запустить приложение и отобразить дизайнер отчетов с загруженным отчетом. Но есть нюанс. Так как WebReportDesigner должен быть расположен в каталоге wwwroot, он не может использовать вью из бэкенда для обработки события сохранения отчета.

На помощь нам придет проксирование клиента на бэкенд. Таким образом мы укажем, что запросы нужно слать на порт сервера, в нашем случае ng сервера.

Чтобы настроить проксирование нужно создать файл proxy.config.json в каталоге src:

1
2
3
4
5
6
7
{
 "/": {
 "target": "http://localhost:4200/",
 "secure": false,
 "logLevel": "debug"
 }
}

В нашем случае порт ng сервера 4200, но он может быть и другим. Можно узнать его, запустив сервер из консоли. Для этого запускаем командную строку Windows переходим к каталогу ClientApp с помощью команды cd, и выполняем: npm start. Из текста, который отобразится дальше вы сможете увидеть нужный номер порта.

Теперь откройте файл package.json в каталоге src и изменим в нем следующие настройки:

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",

 }

Таким образом мы указали файл конфига для проксирования и задали папку wwwroot для сборки ClientApp.

Теперь можно запустить приложение и оценить проделанную работу. Нас ожидает почти пустая страница, только элементы управления:

 

Давайте выберем один из двух отчетов и нажмем кнопку ShowOnlineDesigner:

Отобразился дизайнер с загруженным отчетом. Перейдите на вкладку Report и нажмите кнопку Save:

Если проксирование настроено правильно, то вы увидите сообщение save в зеленой рамке справа. Файл отчета сохранен на сервере.

На этом будем считать, что работа над демонстрационным проектом завершена. Давайте подведем итоги. Разработка бэкенда практически ничем не отличается от обычного ASP .NET Core приложения. Доработка фронтэнда тоже не так сложна, учитывая, что мы сгенерировали почти все файлы одной командой.

4 октября 2023

Как из приложения ASP.NET Core сформировать отчет с использованием FastReport.Core.Skia

Рассказываем как сформировать отчет на Windows и Linux с использованием FastReport.Core.Skia и приватного NuGet сервера.
22 марта 2023

Создание PDF отчета в JetBrains Rider (C#) на «Альт Рабочая станция К» 10

В этой статье мы взглянем на платформу .NET в «Альт Рабочая станция К» 10 и создадим отчет, который можно экспортировать в PDF.
14 февраля 2023

Как настроить веб-сервер Apache2 для FastReport .NET

Запускаем веб-сервер Apache2 в операционной системе Linux для FastReport .NET и .NET 5 с помощью нескольких простых команд.