How to use Online Designer in Single Page Application Angular 7
In this
article we will look at the way to use an online designer in a SPA application
based on the angular framework and ASP .Net Core. Thanks to ASP .Net Core, we
can use the FastReport library in “normal mode”, that is, as in the usual .Net
Core MVC application. In addition, we will use the free FastReport OpenSource
library. Its use is no different from the usual FastReport.Net library.
We will not
consider the use of Angular 1, since it has long been outdated, and it is
unlikely that anyone will be going to write a new project on it. So take the
latest, seventh version of Angular.
Let's look
at the simplest example of creating a SPA application for our requirements.
First you need to install Node.js. This is a platform for running JavaScript
server side applications. Download the distribution for your OS from the
manufacturer’s website https://nodejs.org/en/. Install. Together with Node.js,
NPM, the JavaScript package (library) manager, will also be installed. Of
course, you must have .Net Core SDK 2.0 and higher installed.
And now we run cmd and
move to the directory where you plan to create the project and execute the
command:
dotnet new angular -o SPAOnlineDesigner
Voila, SPA
application is ready. Naturally it is filled with sample pages and examples.
But all this can be removed so as it cannot interfere us.
So, we got
a SPA application, due to lack of necessity we removed unnecessary pages
leaving only the main one. Thus, we will work with only one, single page. We
will place a button on it. By clicking on the button, the online report designer
will be downloaded and displayed.
We need an
online designer. We download it in the client section www.fast-report.com. Let
me remind you that firstly you need to assemble a fresh assembly of an online
designer in the designer. Please note that the Core version must be selected.
After
downloading the archive with the online designer, unzip it to the wwwroot
folder in the project. Also, we need a report file, which we upload to the
online designer, and a database for this report. We will take the report and
the database from the FastReport.Net delivery - the files Master-Detail.frx and
nwind.xml. For them, we will add the App_Data folder to the project root.
Now add
libraries from NuGet. In the package source Nuget.org we find and install the
packages:
- FastReport.OpenSource;
- FastReport.OpenSource.Web;
- Microsoft.Net.Compillers;
- Microsoft.AspNetCore.Razor.Design;
- Microsoft.AspNetCore.SpaServices.Extensions;
- Microsoft.NETCore.App;
- Microsoft.AspNetCore.App.
To use FastReport in your project, you also
need to add the following line to the specified method in the Sturtup.cs file:
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
…
app.UseFastReport();
…
}
Now you can start programming. We have one
controller - SampleDataController. Let's remove all the methods from it. Let's
write our own method, which loads the report into the online designer and
renders the object in HTML:
using System;
using Microsoft.AspNetCore.Mvc;
using FastReport.Web;
using System.IO;
namespace SPAOnlineDesigner.Controllers
{
[Route("api/[controller]")]
public class SampleDataController : Controller
{
[HttpGet("[action]")]
public IActionResult Design()
{
WebReport WebReport = new WebReport();
WebReport.Width = "1000";
WebReport.Height = "1000";
WebReport.Report.Load("App_Data/Master-Detail.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 mode of the web report object - display designer
WebReport.DesignerLocale = "en";
WebReport.DesignerPath = @"WebReportDesigner/index.html"; // We set the URL of the online designer
WebReport.DesignerSaveCallBack = @"SampleData/SaveDesignedReport"; // Set the view URL for the report save method
ViewBag.WebReport = WebReport; // pass the
report to View
return View();
}
[HttpPost]
// call-back
for save the designed report
public ActionResult 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();
}
}
}
In this
class, there are two actions - Design and SaveDesignedReport. The first -
creates a web report object, engages designer mode. The second one sets a
return response to the event of saving the report, saves the report to the
specified folder.
For these
actions you need to create the appropriate presentation. Create the Views
folder in the project root, and inside it the SampleData folder. Add a new
Design view to the folder:
@await
ViewBag.WebReport.Render();
Only one
line - a web report in HTML format.
Add another
SaveDesignedReport view with this content:
@ViewBag.Message
In these two methods there is nothing new for
those who have already worked with an online designer. It is noteworthy that
the actions in the controller are the same as in the usual ASP .Net Core
application.
The main advantage of using angular SPA with
ASP is the ability to make hybrid applications.
Since we only need one page, we will edit the
ClientApp-> src-> app-> app.component.ts file:
import { Component } from '@angular/core';
@Component({
selector: 'app-root',
template: './app.component.html',
styleUrls: ['./app.component.css'],
})
export class AppComponent {
}
For clarity, we will set the page template right here
- in the template property. To download the report designer from the server, we
need an event. This can be OnInit for this component, or, for example, a button
click. Consider both options.
import { Component, OnInit } from '@angular/core';
import { HttpClient } from
"@angular/common/http";
@Component({
selector:
'app-root',
template:
`<div>
<div [innerHTML]="html"></div>
</div>`,
styleUrls:
['./app.component.css']
})
export class AppComponent implements OnInit {
html:
string;
constructor(private http: HttpClient) { }
ngOnInit() {
this.http.get('api/SampleData/Design', { headers: { 'Accept':
'text/html' }, responseType: 'text' as 'text' }).subscribe((data: string) =>
this.html = data);
}
}
Consider this example. We have added an OnInit
event to the import. The AppComponent class now implements this event. To
implement a GET request, the class HttpClient is required. We also add it to
the import. Pass the HttpClient to the constructor of the AppComponent class.
Now in the ngOnInit () method we create a GET
request. In the attributes of the get () method, we specify the url-path to the
controller's method, the header and data type in the response to the request.
Next, we use the subscribe method to listen for
events from the stream that the get method returns. In subscribe, we assign the
received response to the html variable. Now we need to somehow display this
variable on the page:
`<div>
<div
[innerHTML]="html"></div>
</div>`
For this we use the [innerHTML] attribute for
the div tag. With it, you can insert any html code into this tag. However,
there is a nuance - it will be “cleared” html, without styles, embedded
scripts. And we are not satisfied with this, because an online designer is a
complete web application with styles and scripts. Therefore, we need to convert
the html code received by ‘get’ request into a full html document that can be
embedded in the DOM model. To do this, we will add a class SafeHtmlPipe.
Create a new type TypeScript file in the same
folder as app.component.ts. Let's call it safeHtml.pipe.ts:
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);
}
}
Here we will convert the text we received by ‘get’
request into a SafeHtml object.
Now back to app.component.ts. Here is how we
will use the added pipe:
import { Component, OnInit } from '@angular/core';
import { HttpClient } from
"@angular/common/http";
@Component({
selector:
'app-root',
template:
`<div>
<div [innerHTML]="html | safeHtml"></div>
</div>`,
styleUrls:
['./app.component.css']
})
export class AppComponent implements OnInit {
html:
string;
constructor(private http: HttpClient) { }
ngOnInit() {
this.http.get('api/SampleData/Design', { headers: { 'Accept':
'text/html' }, responseType: 'text' as 'text' }).subscribe((data: string) =>
this.html = data);
}
}
But that's not all. To make it work, you need to add a link to this
class in app.module.ts:
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,
SafeHtmlPipe
],
imports: [
BrowserModule.withServerTransition({ appId: 'ng-cli-universal' }),
HttpClientModule,
FormsModule
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule { }
Now we will see our online designer, if we
launch the application. But let's not rush.
As a rule, all interactions with the server
occur through services, so it will be correct to make a GET request to the
service. Create another TypeScript in the same directory. Call it
http.service.ts.
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
@Injectable()
export class HttpService {
constructor(private http: HttpClient) { }
getData() {
return this.http.get('api/SampleData/Design', { headers: { 'Accept': 'text/html' }, responseType: 'text' as 'text' });
}
}
In fact, we simply carried out the GET request
to a separate class. Let's go back to app.component.ts. This is how we will use
this service:
import { Component, OnInit } from '@angular/core';
import { HttpClient } from
"@angular/common/http";
import { HttpService } from "./http.service";
@Component({
selector:
'app-root',
template:
`<div>
<div [innerHTML]="html | safeHtml"></div>
</div>`,
styleUrls: ['./app.component.css'],
providers: [HttpService]
})
export class AppComponent implements OnInit {
html:
string;
constructor(private httpService:
HttpService) { }
ngOnInit() {
this.httpService.getData().subscribe((data:
string) => this.html = data);
}
}
We added the providers parameter to the
component, passed the created service to the constructor of the AppComponent
class, and replaced the GET request to call the getData method from the created
service.
That's all, after running the application, we
will see an online designer. But, we wanted to consider also an example with a
call to the designer by the button. In fact, quite a bit of improvements, take
a look:
import { Component } from '@angular/core';
import { HttpClient } from "@angular/common/http";
import { HttpService } from "./http.service";
@Component({
selector: 'app-root',
template: `<div>
<input
type="button" (click)="Clicked()" value = "Show Online
Designer"/>
<div
*ngIf="flag" [innerHTML]="html | safeHtml"></div>
</div>`,
styleUrls: ['./app.component.css'],
providers: [HttpService]
})
export class AppComponent {
html: string;
flag: boolean;
constructor(private httpService: HttpService) { }
Clicked() {
this.flag = false;
this.httpService.getData().subscribe((data:
string) => { this.html = data; this.flag = true });
}
}
We have
added a button to the page template. Clicking on it executes the Clicked ()
method. In the div that displays the contents of the html variable, another
attribute has been added - * ngIf = “flag”. This condition checks for a flag
variable of Boolean type. We set it to true when we receive a response to a GET
request. This is done in order to exclude the display of an empty html variable
when displaying a web page. We have deleted all references to OnInit. Now we
have a Clicked () method in class. We took its contents from the former
ngOnInit method, only added the flag variable, which is described above.
It's time
to demonstrate how our application works:
So you made
sure that everything is really very simple. In fact, we added only one GET
request to our SPA application. You do not need to have some advanced angular
knowledge to use FastReport OnlineDesigner, and even a beginner can handle it.
Comments
Post a Comment