And I am implementing it like this using Syncfusion's ProgressBar but other controls pretty much use the same parameters:
import { appModuleAnimation } from '@shared/animations/routerTransition';
import { Component, ElementRef, Injector, OnInit, OnChanges, SimpleChanges, ViewChild, ViewEncapsulation, AfterViewInit } from '@angular/core';
import { AppConsts } from '@shared/AppConsts';
import { AppComponentBase } from '@shared/common/app-component-base';
import { FileUpload } from 'primeng/fileupload';
import { HttpHeaders } from '@angular/common/http';
import { ProgressBarDto } from '@app/shared/common/components/progressBar/ProgressBarDto';
import { ProgressBar } from '@syncfusion/ej2-progressbar';
@Component({
selector: 'app-bulkAdjustments',
templateUrl: './bulkAdjustments.component.html',
styleUrls: ['./bulkAdjustments.component.css'],
encapsulation: ViewEncapsulation.None,
animations: [appModuleAnimation()]
})
export class BulkAdjustmentsComponent extends AppComponentBase implements OnInit, AfterViewInit {
notes = '';
uploadUrl: string;
uploadedFiles: any[] = [];
path: any;
errors: string[] = [];
isProcessed = false;
isDisabled = false;
fileSelected = false;
headers = new HttpHeaders();
public progress = new ProgressBarDto();
public showProgress = true;
public gapWidth = 5;
public width = '100%';
public height = '30px';
public trackThickness = 15;
public progressThickness = 15;
@ViewChild('progressBar', { static: false }) progressBar: ProgressBar;
@ViewChild('fileUpload', { static: false }) fileUpload: FileUpload;
@ViewChild('messageLabel', { static: false }) messageLabel: ElementRef;
@ViewChild('progressCountLabel', { static: false }) progressCountLabel: ElementRef;
@ViewChild('totalCountLabel', { static: false }) totalCountLabel: ElementRef;
@ViewChild('transactionIdLabel', { static: false }) transactionIdLabel: ElementRef;
constructor(injector: Injector) {
super(injector);
}
ngOnInit(): void {
super.ngOnInit();
this.notes = '';
this.uploadUrl = AppConsts.remoteServiceBaseUrl + '/Adjustments/ProcessBulkAdjustments';
this.path = { saveUrl: this.uploadUrl };
this.headers.set('Authorization', 'Bearer ' + abp.auth.getToken());
abp.event.on('app.progress.received', (progress) => {
this.updateProgress(new ProgressBarDto(progress));
});
}
ngAfterViewInit() {
setTimeout(() => {
this.showProgress = false;
}, 1);
}
updateProgress(progress: ProgressBarDto): void {
let p = progress;
if (this.progressBar.segmentCount !== p.totalCount) {
this.progressBar.segmentCount = p.totalCount;
this.totalCountLabel.nativeElement.innerHTML = p.totalCount;
this.progressBar.refresh();
}
// If the message is different, update the label.
if (this.messageLabel.nativeElement.innerHTML !== p.message) {
this.messageLabel.nativeElement.innerHTML = p.message;
}
this.progressBar.value = p.percentComplete;
this.progressCountLabel.nativeElement.innerHTML = p.progressCount;
// Grab the value from the custom data.
this.transactionIdLabel.nativeElement.innerHTML = p.data?.transactionId;
this.progress = p;
// Initially, the divs will not refresh until the area is clicked on.
document.getElementById('progressBarParent').click();
}
reloadPage() {
location.reload();
}
processBulkAdjustments() {
this.progress = new ProgressBarDto();
this.showProgress = true;
this.isProcessed = false;
this.fileUpload.upload();
}
onSelect(event: any) {
this.fileSelected = event.currentFiles.length > 0;
}
onUpload(event: any): void {
this.isProcessed = true;
this.fileSelected = false;
this.notes = '';
this.errors = event.originalEvent.body.result;
}
onBeforeUpload(event: any): void {
event.formData.append('notes', this.notes);
}
}
bulkAdjustments.component.html:
<div [@routerTransition] [busyIf]='isBusy'>
<div class='content d-flex flex-column flex-column-fluid'>
<sub-header [title]="'Adjustments' | localize" [description]="'BulkAdjustments' | localize"></sub-header>
<div [class]='containerClass'>
<div class='card card-custom'>
<div class='card-body'>
<div *ngIf='isProcessed' class='row'>
<div class='col-md-12'>
<div *ngIf='errors?.length > 0' class='margin-bottom-25'>
<div class='alert alert-danger' role='alert'>
<div>{{'ErrorsDuringProcessing' | localize}}</div>
</div>
<ul>
<li *ngFor='let error of errors'><div [innerHTML]='convertLineBreaks(error)'></div></li>
</ul>
<hr/>
</div>
<div *ngIf='!errors || errors.length === 0' class='alert alert-success' role='alert'>
{{"ProcessedSuccessfully" | localize}}
</div>
</div>
</div>
<div class='row'>
<div class='col-md-4 padding-bottom-10'>
<div class='font-small padding-left-20'>{{'SelectExcelFile' | localize}}</div>
<p-fileUpload #fileUpload
mode='advanced'
[url]='uploadUrl'
[showUploadButton]='false'
[headers]='headers'
(onSelect)='onSelect($event)'
(onUpload)='onUpload($event)'
(onBeforeUpload)='onBeforeUpload($event)'>
</p-fileUpload>
</div>
</div>
<div class='row'>
<div class='col-md-12 padding-top-20'>
<h5>{{'Notes' | localize}}:</h5>
<input type='text' id='notes' name='notes' [(ngModel)]='notes' placeholder='Required' autocomplete='off' class='form-control' />
</div>
</div>
<div class='row'>
<div class='col-md-12 padding-top-20'>
<button class='btn btn-info' *ngIf='!isDisabled && notes' (click)='processBulkAdjustments()' [buttonBusy]='isBusy' [busyText]="l('SavingWithThreeDot')">
<i class='fa fa-upload'></i>
<span>{{"Process" | localize}}</span>
</button>
<div><br /><span [innerHTML]="l('BulkAdjustments_ExcelFileRequirement')"></span></div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div *ngIf='showProgress && !isProcessed' id="overlay"></div>
<div *ngIf='showProgress && !isProcessed' id='progressBarParent' class='progressBarParent'>
<div class='progressBarContainer'>
<div class='progressBar'>
<ejs-progressbar #progressBar type='Linear' cornerRadius='Square'
[width]='width' [height]='height' [gapWidth]='gapWidth'
[trackThickness]='trackThickness' [progressThickness]='progressThickness'
[showProgressValue]='true'>
</ejs-progressbar>
<div [hidden]='progress.progressCount !== 0' class='font-larger'>
{{'LoadWithThreeDot' | localize}}
</div>
<div [hidden]='progress.progressCount === 0'>
<div class='font-larger'>
<label #messageLabel></label>
<label #progressCountLabel></label> {{l('of').toLowerCase()}} <label #totalCountLabel></label>
</div>
<div class='padding-top-5 font-larger'>
{{'TransactionId' | localize}}: <label #transactionIdLabel></label>
</div>
</div>
<div class='padding-top-10'>
<button class='btn btn-danger' (click)='reloadPage()'><i class='fas fa-times'></i>{{'Cancel' | localize}}</button>
</div>
</div>
</div>
</div>
In my AdjustmentsController I have the following (I've removed some code for brevity):
namespace MyApp.Web.Controllers
{
public class AdjustmentsController : MyAppControllerBase
{
private readonly ISignalRCommon _progressBar;
private readonly IAppFolders _appFolders;
public AdjustmentsController(
ISignalRCommon progressBar,
IAppFolders appFolders)
{
_progressBar = progressBar;
_appFolders = appFolders;
}
[HttpPost]
public virtual async Task> ProcessBulkAdjustments()
{
var files = Request.Form.Files;
var notes = Request.Form["notes"];
var errors = new List();
if (notes.IsNullOrEmpty())
{
errors.Add("Missing notes.");
return errors;
}
if (files == null)
{
errors.Add("No files found to process.");
return errors;
}
foreach (var file in files.Where(q => q != null && q.Length > 0))
{
var error = await BulkAdjustments(file, notes);
if (!error.IsNullOrWhiteSpace()) errors.Add($"{file.FileName}\r\n{error}");
}
return errors;
}
public async Task BulkAdjustments(IFormFile file, string notes)
{
var fileName = Path.GetFileName(file.FileName);
if (fileName.IsNullOrWhiteSpace()) return L("File_Empty_Error");
//New instance of ExcelEngine is created
//Equivalent to launching Microsoft Excel with no workbooks open
//Instantiate the spreadsheet creation engine
var excelEngine = new ExcelEngine();
//Instantiate the Excel application object
var application = excelEngine.Excel;
//var xlFile = new FileStream(tempFilePath, FileMode.Open);
var xlFile = file.OpenReadStream();
var workbook = application.Workbooks.Open(xlFile);
var worksheet = workbook.Worksheets.FirstOrDefault();
if (worksheet == null) return "No worksheet found";
var start = worksheet.UsedRange.Row;
var end = worksheet.UsedRange.LastRow;
var col = worksheet.UsedRange.Column;
var bulkUpdates = new List<SomeDataDto>();
for (var row = start + 1; row <= end; row++)
{
if (!int.TryParse(worksheet.Range[row, col].CalculatedValue, out var transactionId)) continue;
if (!int.TryParse(worksheet.Range[row, col + 1].CalculatedValue, out var userId)) continue;
var someDataDto = new SomeDataDto
{
TransactionId = transactionId,
UserId = userId
};
bulkUpdates.Add(someDataDto);
await _progressBar.SendProgress(L("ParsedRow"), row, end, someDataDto);
}
var count = 0;
var totalCount = bulkUpdates.Count;
var errors = string.Empty;
foreach (var bulkUpdate in bulkUpdates)
{
count++;
await _progressBar.SendProgress(L("PreProcessingBulkUpdate"), count, totalCount, bulkUpdate);
// Do something.
}
// Group the list by the UserId.
var groupedBulkUpdates = bulkUpdates
.Where(q => q.UserId.HasValue)
.GroupBy(g => g.UserId)
.Select(s => s.ToList())
.ToList();
count = 0;
foreach (var bulkUpdateGroup in groupedBulkUpdates)
{
foreach (var bulkUpdate in bulkUpdateGroup)
{
count++;
await _progressBar.SendProgress(L("ProcessingBulkUpdate"), count, totalCount, bulkUpdate);
// Do something.
// errors = await doSomething();
}
await CurrentUnitOfWork.SaveChangesAsync();
}
await _progressBar.SendProgress(L("ProcessingComplete"), count, totalCount);
return errors;
}
}
}
In MyApp.Core/Common, I have ISignalRCommon.cs:
namespace MyApp.Common
{
public interface ISignalRCommon
{
Task SendProgress(string message, int progressCount, int totalCount, object data = null);
}
}
In MyApp.Web.Core/Common/SignalR, I have CommonHub.cs and SignalRCommon.cs:
namespace MyApp.Web.Common.SignalR
{
public class CommonHub : Hub
{
}
}
namespace MyApp.Web.Common.SignalR
{
public class SignalRCommon : ISignalRCommon, ITransientDependency
{
private readonly IHubContext _commonHub;
public SignalRCommon(IHubContext commonHub)
{
_commonHub = commonHub;
}
public async Task SendProgress(string message, int progressCount, int totalCount, object data = null)
{
var progress = new ProgressDto
{
Message = message,
ProgressCount = progressCount,
PercentComplete = (progressCount * 100) / totalCount,
TotalCount = totalCount,
Data = Newtonsoft.Json.JsonConvert.SerializeObject(data)
};
await _commonHub.Clients.All.SendAsync("sendProgress", progress);
return progress;
}
}
}
Finally, ProgressDto:
namespace MyApp.Common.Dto
{
public class ProgressDto
{
public string Message { get; set; } = string.Empty;
public decimal ProgressCount { get; set; } = 0;
public decimal PercentComplete { get; set; } = 0;
public decimal TotalCount { get; set; } = 0;
public string Data { get; set; } = string.Empty;
}
}
Hi @dzungle, sorry for the very late reply.
I've broken it down into what I hope will make sense and hope that I haven't forgotten anything.
In app.component.ts and app.module.ts, I import the service:
import { CommonSignalrService } from '@app/shared/common/signalR/common-signalr.service';
In app.component.ts add to constructor:
private _commonSignalrService: CommonSignalrService,
and init with the chat signalr service:
if (this.appSession.application) {
SignalRHelper.initSignalR(() => {
this._chatSignalrService.init();
this._commonSignalrService.init();
});
}
The common-signalr.service.ts:
import { Injectable, Injector, NgZone } from '@angular/core';
import { AppComponentBase } from '@shared/common/app-component-base';
import { HubConnection } from '@microsoft/signalr';
@Injectable()export class CommonSignalrService extends AppComponentBase {
constructor(
injector: Injector,
public _zone: NgZone
) { super(injector);
}
commonHub: HubConnection;
isCommonConnected = false;
configureConnection(connection): void { // Set the common hub
this.commonHub = connection;
// Reconnect loop
let reconnectTime = 5000;
let tries = 1;
let maxTries = 8;
function start() { return new Promise(function (resolve, reject) {
if (tries > maxTries) {
reject();
} else { connection.start()
.then(resolve)
.then(() => {
reconnectTime = 5000;
tries = 1;
}) .catch(() => {
setTimeout(() => {
start().then(resolve);
}, reconnectTime);
reconnectTime *= 2;
tries += 1;
});
} });
}
// Reconnect if hub disconnects
connection.onclose(e => { this.isCommonConnected = false;
if (e) { abp.log.debug('Common signalR connection closed with error: ' + e);
} else { abp.log.debug('Common signalR disconnected');
}
start().then(() => {
this.isCommonConnected = true;
});
});
// Register to get notifications
this.registerCommonEvents(connection);
}
registerCommonEvents(connection): void {
connection.on('sendProgress', (progress) => {
abp.event.trigger('app.progress.received', progress);
});
}
init(): void {
this._zone.runOutsideAngular(() => {
abp.signalr.connect();
abp.signalr.startConnection(abp.appPath + 'signalr-common', connection => { this.configureConnection(connection);
}).then(() => { abp.event.trigger('app.common.connected');
this.isCommonConnected = true;
});
});
}}
My ProgressBarDto:
import { Inject, Injectable } from '@angular/core';
@Injectable()export class ProgressBarDto implements IProgressBarDto {
message = '';
progressCount = 0;
percentComplete = 0;
totalCount = 0;
data: any;
constructor(@Inject(ProgressBarDto) data?: IProgressBarDto) { if (data) {
for (let property in data) {
if (data.hasOwnProperty(property)) {
(<any>this)[property] = (<any>data)[property];
} }
this.data = JSON.parse(data['data']);
} }
static fromJS(data: any): ProgressBarDto {
data = typeof data === 'object' ? data : {};
let result = new ProgressBarDto();
result.init(data);
return result;
}
init(data?: any) {
if (data) {
this.message = data['message'];
this.progressCount = data['progressCount'];
this.percentComplete = data['percentComplete'];
this.totalCount = data['totalCount'];
this.data = JSON.parse(data['data']);
} }
toJSON(data?: any) {
data = typeof data === 'object' ? data : {};
data['message'] = this.message;
data['progressCount'] = this.progressCount;
data['percentComplete'] = this.percentComplete;
data['totalCount'] = this.totalCount;
data['data'] = JSON.stringify(this.data);
return data;
}}
export interface IProgressBarDto {
message: string;
progressCount: number;
percentComplete: number;
totalCount: number;
data: any;
}
Hi @musa.demir, @ismcagdas,
I found the issue - it was a typo in the event name in the controller - "abp." instead of "app.".
Wg
Hi @musa.demir, @ismcagdas,
I have initialized my signalr service along with the chat service:
if (this.appSession.application) {
SignalRHelper.initSignalR(() => {
this._chatSignalrService.init();
this._commonSignalrService.init();
});
}
In my service, this is working and I am receiving the messages from the server:
registerCommonEvents(connection): void {
connection.on('sendProgress', (progress) => {
console.log('sendProgress', progress);
abp.event.trigger('app.progress.received', progress);
});
}
However, in my component, this is not working:
ngOnInit(): void {
super.ngOnInit();
abp.event.on('abp.progress.received', (progress) => {
console.log('progressReceived', progress);
this.progress = progress;
});
}
Any thoughts?
Thanks,
Wg
Hi @ismcagdas,
Is there any code I can look at to do this?
Thanks,
Wg
Greetings Programs!
RE: AspNetZero - Angular + Core 10.2.
I am uploading an excel file that I loop through the rows and processing them.
I would like to be able to use a progress bar (https://ej2.syncfusion.com/angular/demos/#/bootstrap5/progress-bar/Linear) so that I can display which row is currently being processed.
Here is my controller method for processing the file; within the loop at the end, I would like to pass back the current row value to the angular component.
Any thoughts as to how I can acheive this?
Thanks,
Wg
public async Task BulkPayrollAdjustments(IFormFile file, string notes)
{
var fileName = Path.GetFileName(file.FileName);
if (fileName.IsNullOrWhiteSpace()) return L("File_Empty_Error");
//New instance of ExcelEngine is created
//Equivalent to launching Microsoft Excel with no workbooks open
//Instantiate the spreadsheet creation engine
var excelEngine = new ExcelEngine();
//Instantiate the Excel application object
var application = excelEngine.Excel;
//var xlFile = new FileStream(tempFilePath, FileMode.Open);
var xlFile = file.OpenReadStream();
var workbook = application.Workbooks.Open(xlFile);
var worksheet = workbook.Worksheets.FirstOrDefault();
if (worksheet == null) return "No worksheet found";
var start = worksheet.UsedRange.Row;
var end = worksheet.UsedRange.LastRow;
var col = worksheet.UsedRange.Column;
var bulkUpdates = new List<BulkUpdateDto>();
// Get the rows.
for (var row = start + 1; row <= end; row++)
{
var bulkUpdateDto = new BulkUpdateDto
{
// Create object from row.
FromBaseId = worksheet.Range[row, col].CalculatedValue;
// ...
};
bulkUpdates.Add(bulkUpdateDto);
}
//Close the instance of IWorkbook
workbook.Close();
//Dispose the instance of ExcelEngine
excelEngine.Dispose();
var currentRow = 1;
foreach (var bulkUpdate in bulkUpdates)
{
//*****************************************************************
// I would like to send the current row number value here.
//*****************************************************************
sendCurrentRowNumberToComponent(currentRow);
await _appService.ProcessUpdate(bulkUpdate)
currentRow++;
await CurrentUnitOfWork.SaveChangesAsync();
}
}
Hi @billyteng,
Thanks very much! It worked for me too.
Wg
Hi @ismcagdas,
With a new, manually generated 10.1, project, I am now receiving the following error when I try to remove the table prefix.
Thanks,
Wg
Applying migration '20201217075257_Upgrade_To_ABP_6_1'.
Done.
PM> add-migration Remove_Abp_Prefix -context ptadbcontext
Build started...
Build succeeded.
An operation was scaffolded that may result in the loss of data. Please review the migration for accuracy.
To undo this action, use Remove-Migration.
PM> update-database
Build started...
Build succeeded.
Applying migration '20210114221539_Remove_Abp_Prefix'.
System.InvalidOperationException: To change the IDENTITY property of a column, the column needs to be dropped and recreated.
at Microsoft.EntityFrameworkCore.Migrations.SqlServerMigrationsSqlGenerator.Generate(AlterColumnOperation operation, IModel model, MigrationCommandListBuilder builder)
at Microsoft.EntityFrameworkCore.Migrations.MigrationsSqlGenerator.<>c.<.cctor>b__83_4(MigrationsSqlGenerator g, MigrationOperation o, IModel m, MigrationCommandListBuilder b)
at Microsoft.EntityFrameworkCore.Migrations.MigrationsSqlGenerator.Generate(MigrationOperation operation, IModel model, MigrationCommandListBuilder builder)
at Microsoft.EntityFrameworkCore.Migrations.SqlServerMigrationsSqlGenerator.Generate(MigrationOperation operation, IModel model, MigrationCommandListBuilder builder)
at Microsoft.EntityFrameworkCore.Migrations.MigrationsSqlGenerator.Generate(IReadOnlyList`1 operations, IModel model, MigrationsSqlGenerationOptions options)
at Microsoft.EntityFrameworkCore.Migrations.SqlServerMigrationsSqlGenerator.Generate(IReadOnlyList`1 operations, IModel model, MigrationsSqlGenerationOptions options)
at Microsoft.EntityFrameworkCore.Migrations.Internal.Migrator.GenerateUpSql(Migration migration, MigrationsSqlGenerationOptions options)
at Microsoft.EntityFrameworkCore.Migrations.Internal.Migrator.<>c__DisplayClass16_2.<GetMigrationCommandLists>b__2()
at Microsoft.EntityFrameworkCore.Migrations.Internal.Migrator.Migrate(String targetMigration)
at Microsoft.EntityFrameworkCore.Design.Internal.MigrationsOperations.UpdateDatabase(String targetMigration, String connectionString, String contextType)
at Microsoft.EntityFrameworkCore.Design.OperationExecutor.UpdateDatabaseImpl(String targetMigration, String connectionString, String contextType)
at Microsoft.EntityFrameworkCore.Design.OperationExecutor.UpdateDatabase.<>c__DisplayClass0_0.<.ctor>b__0()
at Microsoft.EntityFrameworkCore.Design.OperationExecutor.OperationBase.Execute(Action action)
To change the IDENTITY property of a column, the column needs to be dropped and recreated.
Thanks @ismcagdas; the project was generated using the generate latest drop down in the download history section.
A new project was generated manually using the .NET 5 selection and it ran successfully this time without any issues; I assume that it did not update the framework when generating from tthe download history.
I look forward to 10.1 to fix the change prefix issue (or perhaps a specific fix that I can implement sooner).
Thanks,
Wg
Hi @ismcagdas,
I will create an issue on GitHub.
I downloaded the project today @ 10:15am MST. The error I receive for all the projects is:
NU1202 Package Abp.AspNetZeroCore 3.0.0 is not compatible with netcoreapp3.1 (.NETCoreApp,Version=v3.1). Package Abp.AspNetZeroCore 3.0.0 supports: net50 (.NETFramework,Version=v5.0) PTA.Web.Host
Thanks,
Wg