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

Popular posts

How to use FastReport Open Source in ASP.NET Core Web API application

FastReport Open Source - what is it and how to use it

FastReport Designer Community Edition