Base solution for your next web application

Activities of "cyklussoftware"

I am using version ASP.NET Core + Angular v6.5.0.

I basically copied the image uploading logic from the Profile Picture file upload component and I am having an issue when my custom modal is closing. My code is attached below.

When my close() function runs the uploader.clearQueue(); line, an exception that says TypeError: Cannot read property 'isUploading' of undefined gets thrown. This is part of the file-uploader.class.js file. I think the function that is throwing the exception looks like this:

FileUploader.prototype.removeFromQueue = function (value) {
        var index = this.getIndexOfItem(value);
        var item = this.queue[index];
        if (item.isUploading) {
            item.cancel();
        }
        this.queue.splice(index, 1);
        this.progress = this._getTotalProgress();
};

What's odd is that when I debug this in VS Code, the item variable is defined and isUploading is also defined.

My image upload works and I can see the images reflected on my server, but after running the this.uploader.uploadAll(); function, the queue is not empty. If I check the queue after uploading in the Profile Picture section, the queue is empty. I'm not sure what could be going wrong given that my code is practically a copy. Maybe I'm missing an import somewhere that doesn't throw an error when it's missing?

My component code is below.

import { IAjaxResponse } from '@abp/abpHttpInterceptor';
import { TokenService } from '@abp/auth/token.service';
import { Component, Injector, ViewChild } from '@angular/core';
import { AppConsts } from '@shared/AppConsts';
import { AppComponentBase } from '@shared/common/app-component-base';
import { FileUploader, FileUploaderOptions, FileItem } from 'ng2-file-upload';
import { ModalDirective } from 'ngx-bootstrap';
import { finalize } from 'rxjs/operators';

@Component({
    selector: 'changeRewardImageModal',
    templateUrl: './change-reward-image-modal.component.html'
})
export class ChangeRewardImageModalComponent extends AppComponentBase {

    @ViewChild('changeRewardImageModal') modal: ModalDirective;

    public active = false;
    public uploader: FileUploader;
    public temporaryPictureUrl: string;
    public saving = false;

    private maxProfilPictureBytesUserFriendlyValue = 5;
    private temporaryPictureFileName: string;
    private _uploaderOptions: FileUploaderOptions = {};

    imageChangedEvent: any = '';

    constructor(
        injector: Injector,
        private _tokenService: TokenService
    ) {
        super(injector);
    }

    initializeModal(): void {
        this.active = true;
        this.temporaryPictureUrl = '';
        this.temporaryPictureFileName = '';
        this.initFileUploader();
    }

    show(): void {
        this.initializeModal();
        this.modal.show();
    }

    close(): void {
        console.log("close");
        this.active = false;
        this.imageChangedEvent = '';

        // Clearing the queue gets rid of the files that need to be uploaded.
        // Idk if we actually need to do this if the modal is closing.
        //console.log(this.uploader.queue.length);
        //if(this.uploader.queue.length > 0){
        try {
            this.uploader.clearQueue(); // todo this was causing an exception to be thrown
        } catch (exception){
            console.log(exception);
        }
        //}

        this.modal.hide();
    }

    fileChangeEvent(event: any): void {
        this.imageChangedEvent = event;
    }

    imageCroppedFile(file: File) {
        console.log("cropped file");
        let files: File[] = [file];
        this.uploader.clearQueue();
        this.uploader.addToQueue(files);
    }

    initFileUploader(): void {
        this.uploader = new FileUploader({ url: AppConsts.remoteServiceBaseUrl + '/Reward/UploadRewardImage' });
        this._uploaderOptions.autoUpload = false;
        this._uploaderOptions.authToken = 'Bearer ' + this._tokenService.getToken();
        this._uploaderOptions.removeAfterUpload = true;
        this.uploader.onAfterAddingFile = (file) => {
            file.withCredentials = false;
        };

        this.uploader.onBuildItemForm = (fileItem: FileItem, form: any) => {
            form.append('FileType', fileItem.file.type);
            form.append('FileName', 'RewardImage');
            form.append('FileToken', this.guid());
        };

        this.uploader.onSuccessItem = (item, response, status) => {
            const resp = <IAjaxResponse>JSON.parse(response);
            if (resp.success) {
                this.saving = false;
                abp.event.trigger('rewardImageUploaded', {
                    fileToken: resp.result.fileToken,
                    x: 0,
                    y: 0,
                    width: 0,
                    height: 0
                });
                this.close();
            } else {
                this.message.error(resp.error.message);
            }
        };

        this.uploader.setOptions(this._uploaderOptions);
    }

    guid(): string {
        function s4() {
            return Math.floor((1 + Math.random()) * 0x10000)
                .toString(16)
                .substring(1);
        }
        return s4() + s4() + '-' + s4() + '-' + s4() + '-' + s4() + '-' + s4() + s4() + s4();
    }

    save(): void {
        if(this.uploader.queue.length > 0){
            this.saving = true;
            this.uploader.uploadAll();
        }
    }
}

@alper @rbreton This probably isn't related, but there was an issue that I came across the other day while generating an Entity called EventCategory.

This was the problematic code before:

modelBuilder.Entity<EventCategory>(e =>
{
    e.HasIndex(e => new { e.TenantId });
});

And after I changed the name of my entity:

modelBuilder.Entity<SchoolEventCategory>(s =>
{
    s.HasIndex(e => new { e.TenantId });
});

The tool picks what to call the first variable based on the first letter of the entity name. So if anything starts with E, there will be a compile error.

So, like I said, it's unrelated but a problem.

@rbreton Did you try building your project after generating all of the code? The specific error would likely show up then.

I've seen some other posts asking about how to change the branding for individual Tenants. I'm not sure if this is the best place to post this, but over the past 2 days I have been looking into and working on changing themes for ASP.NET Core + Angular v6.5. I might have a solution that will work for some people.

I created a tool that programatically edits the Default theme's CSS files by comparing two different included themes (Default and Pink, for example) and determining which colors are shared and which colors are unique between the two. The CSS that is output works based on CSS variables (https://www.w3schools.com/css/css3_variables.asp). This means that there is a only a single place that colors are defined. With this system, Tenants can upload a CSS document that contains updated variable definitions to reflect their primary branding color. These updated variables overwrite the original variables that the tool outputs, so the web page uses the Tenant colors instead of whatever default color you use.

The tool will generate the entire theme (similar to the Metronic Theme Changer here https://github.com/aspnetzero/metronic, but it doesn't require any new scss to be compiled via Gulp. The outputed Default theme contains the variable references needed to implement branding for individual Tenants. As mentioned, Tenants can change their colors if they upload a CSS document with overriding variable definitions.

I think this is the solution I am going to use. As far as I know, the "official" method might require creating a new theme for every Tenant and baking it directly into the application by default. I could be wrong and there might be a better way to do things. Either way, this was an interesting project to work on for a few days.

You can find the source code (C#) here: https://github.com/Connor14/CSSThemeHelper. Feel free to contribute where you see fit. The tool doesn't minify the css and it doesn't output a dedicated tenant CSS document without overwriting the output files that you specify. It also doesn't consolidate the large number of colors that there are. I have found 62 colors, but I think I forgot to include a CSS file from somewhere. The Metronic Theme Changer project provided by ASP.NET Zero has 69 colors listed and the files that I am pulling from only contained 62.

I hope this helps someone!

@ismcagdas

I wasn't trying to complain about the number of colors. I was just saying that my tool doesn't consolidate colors that are very similar in color value. I played around with consolidating colors based on the HSV color space, but the version I put on GitHub doesn't do that. It is a potential improvement for my tool.

I wanted to post here to let people know that I made a tool that helped me with my Tenant Custom CSS problem. I am hoping that someone else might find it useful.

@chauey We pushed custom branding futher back in our backlog, so I haven't worked on this tool for a while. I think the biggest thing it needs is a way to export a tenant-specific CSS file for them to upload.

Feel free to expand or even rewrite the existing logic.

I am using ASP.NET Zero v7.1 with ASP.NET Core and Angular.

I am looking into adding a Unique Constraint to a custom column I added the AbpUsers table. It is a string column with nvarchar(256), like Username. While I was looking through the table to see how the Username column's uniqueness is enforced, I realized that it isn't enforced directly in the database. I am able to insert duplicate rows if I insert directly using SQL. If I try to create a duplicate email or username when registering through the application, I get the error that says it is a duplicate.

This leads me to believe that uniqueness is checked somewhere in the UserManager when creating the user. Is this a correct assumption? Is there a specific reason that unique contraints aren't enforced in this table (and maybe others too)?

I would love to know the reasoning behind this decision so that I can learn from it. Is it because it is a multi-tenant application? Maybe because the AbpUsers table uses soft-delete? I've been adding unique indexes to my custom database tables so that duplicates are strictly forbidden. With the unique indexes I can't accidentally create duplicate data if I introduce a bug in my code. My tables don't have soft-delete so I haven't run into issues with that. And I haven't run into issues with uniqueness across tenants because my unique indexes refer to Entity row Ids (rows in my many-to-many relationship tables needed unique constraints, for example). I suppose that the unique constraint could cause problems if I try to scale a single tenant across multiple databases, but I don't know if that is possible with ASP.NET Zero.

Is there a reason why I shouldn't be creating unique indexes in a multi-tenant application?

I'm looking forward to hearing your thoughts!

Hello,

I am using ASP.NET Zero v7.1 with .NET Core 2.2 and Angular.

In my application, we have many Organization entities. Organizations needs their own permission/authorization system so that members of the organization can be given different roles and abilities within the Organization. We refer to the default ASP.NET Zero permission system as the "Global Permission" system and the Organization based permission system as the "Organization Permission" system.

Our Organization Permission system is not meant to be as comprehensive as the Global Permission system. We simply have 4 Permission Levels with a predefined set of abilities. For example:

  • The Level 3 Permission Level can add Members and modify Organization details like it's logo and description.
  • The Level 2 Permission Level can create/update/delete Announcements or other Organization specific entities unrelated to Member management
  • The Level 1 Permission Level has no special permissions, but can see Organization specific entities assigned to them (or other private Organization information)
  • The "Public" Permission Level doesn't actually exist. When a user is in the "Public" Permission Level, it means that a user doesn't belong to the Orgnaization and can only see Organization specific entities marked as "IsPublic"

Orgnanization specific entities like Announcement can be marked as IsPublic OR be assigned to 1 or more Permission Levels.

Now, when a User wants to see a list of Announcements for an Organization, we query the database for all Announcements related to Organization 1 and check 4 things in the WHERE condition of our query:

  • Does the User have any Global Permissions related to Announcements? If yes, then he can view ALL Announcements
  • Is the Announcement Public? If yes, then he can view the Announcement
  • Does the User have a Level 2 or higher Permission Level for the Organization that this Announcement belongs to? If yes, then he can view the Announcement.
  • Is the Announcement assigned to his Permission Level in the Organization? If yes, then he can view the Announcement.

Checking the 4 conditions in the WHERE of our query works, but it requires us to query for a list of the User's Memberships before doing the Announcement query so that the Announcement query can check the User's permission for each Announcement without doing extra JOIN / GROUP BY logic. I am working to make this easier and more efficient by using a TypedCache and a helper class.

When it comes to creating/updating/deleting Announcements, a User must have Global Permission OR Level 2 or higher for the Organization. Right now, this is done doing another query inside of each Create/Update/Delete method (I already have a helper class doing it), but I would like to abstract the permission checking logic into an Attribute or Filter, if possible, so that I don't have to write as much code each time I create a new create/edit/delete endpoint.

Hopefully what I have described makes sense to some degree. Based on what I've described, do you think it's worth trying to go down the custom Authorization Filter / Authorization Policy route for create/update/delete? Or do you think it makes sense to keep querying like I am because it is a non-standard Authorization scheme?

Thanks!

I am upgrading from ASP.NET Zero v7.1 (.NET Core 2.2 + Angular) to ASP.NET Zero v8.1 (.NET Core 3.1 + Angular) and I noticed that ALL of the migrations (including the Initial Migration) have changed.

I believe the only change to the previous migrations was that each and every column in the Designer.cs files now have .HasColumnType applied to them. Is this considered a major breaking change for upgrading from previous versions of ASP.NET Zero or from EF Core 2.2 to EF Core 3.1?

Also, for general reference, after generating a migration using the RAD tool, I would manually change the Delete Behavior from CASCADE to RESTRICT. But I never updated the Designer files. Is it necessary to update the designer files or somehow re-create them? Or is it OK to simply update the main Up and Down methods?

Thanks

@maliming Thanks for pointing me to those issues. When it comes to Entity Framework Core, would those changes be considered breaking changes? By breaking change, I mean a change that shouldn't be applied to a database that is currently in production. Or, if I create a new database for a new tenant, would it create an inconsistency between the existing database and the new database?

I'll take a look. Thanks!

Showing 31 to 40 of 42 entries