Base solution for your next web application

Activities of "wizgod"

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>&nbsp;
                    <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

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

Hi @maliming,

Yes, thanks,

Wg

Showing 1 to 10 of 36 entries