Thanks for your response. We still have not deployed to prod. The performance issues are happening in our QA Environment. The current getall() method takes 20+ seconds to load. I attached the screenshot. Also i am looking to minify all css and js files. If you can please help where to do it. thanks
thanks a lot for you help. It works fine now.
Hi , We are running into performance issues with the .net core with angular app. Can you please guide in the best way to help improve performance
Are there libraries that come with aspnetzero / abp that we're not using that could be deleted? or not allow to load at start up ?
Are there resources that it would make sense to delay loading until after login page (like do in parallel with Okta login API call or load in background after user hits the main landing page )?
How can we Ensure all .css and .js is minified for all our environments ?> Is there a common place we can do that ?
Thank you very much maliming! Is that because of the implementation of IMultiTenantLocalizationSource causing multiple trips to cache data store?
Yeah, the Azure Redis cluster performance is 10 times worse than my local as it's cross data center. The cluster's IOPS is not bad but network performance causes the delay.. Will investigate more on that.
Best regards,
Hi Abp Zero,
We're using .net Core 2.1 and Angular 8.
For certain reason I would like to overwrite MultiTenantLocalizationSource with my own CustomMultiTenantLocalizationSource.
I tried to use following code in the PreInitialize() in ProjectCoreModule but it didn't work as other replaced services:
Configuration.ReplaceService<IMultiTenantLocalizationSource, CustomMultiTenantLocalizationSource>(DependencyLifeStyle.Transient);
Could you please advise how I coud replace the IMultiTenantLocalizationSource service?
Thank you
I think I get that every localization is calling redis cache, even following simple line in GetRoleForEdit method (could you give some hint how this happens? I don't get how it reaches out the cache in following line):
ObjectMapper.Map<List<FlatPermissionDto>>(permissions).OrderBy(p => p.DisplayName).ToList();
Thank you
Thank you so much for quick turnaround, maliming!
The performance improved a lot as it doesn't have duplicate calls to retrieve 'abpzerorolepermissions'. But there are still some duplicate calls (50+, based on permissions we have I guess) to "AbpZeroMultiTenantLocalizationDictionaryCache" and "AbpZeroLanguages".
Also there is also similar issue with GetLanguageTexts (These two places might be caused by the same code). Could you please help look into that one as well? When I click on ChangeTexts Then in redis:
Update:
Seems issue comes from MultiTenantLocalizationSource.GetStringOrNull and then MultiTenantLocalizationDictionary.GetOrNull. https://github.com/aspnetboilerplate/aspnetboilerplate/blob/dev/src/Abp.Zero.Common/Localization/MultiTenantLocalizationSource.cs https://github.com/aspnetboilerplate/aspnetboilerplate/blob/dev/src/Abp.Zero.Common/Localization/MultiTenantLocalizationDictionary.cs
This line is reading from cache every time.
Hi,
Our product is based on Core2.1 + Angular8, and we planned to turn on Redis for cache.
Before we turn on Redis, the default memory cache was being used.
Take GetRoleForEdit as an example,
Before Redis was turned on, The EditRole modal was rendered in 1-2 seconds.
After Redis was turned on: Whenever we click on GetRoleForEdit, it takes long time (more than 50s+, sometimes timeout) to render the modal.
Same thing happens for my local redis setup -- it takes 6.08 seconds for the result to come back when I use redis on my local workstation.
For GetLanguageTexts endpoint, the longest request duration was even 19min today.
One of assumptions is that Redis is reflecting what's happening under the hood -- GetRoleForEdit is visiting cache hundreds of time, and the performance issue emerges after we turned on Redis. Another assumption is the some logic in AbpRedisCacheManager causes the 'infinite' calls to Redis cache.
Please advise why this performance issue happens after turning on Redis.
Thank you,
Thanks for your response.. Our requirement is for example in canada - they have 2 lanaguages . english and french. we want the keep date format 2020-04-13 for both these languages based on their browser settings. . The language text needs to change based on the language they select from the Languages dropdown . But the date format on all the grids needs to be driven based on the browser settings ? Does so does this code take care of all browsers ?
moment.locale(navigator.language); (window as any).moment.locale(navigator.language);
Hi I could not find the configureMoment method you talked above ? Here's the code from AppPreBootstrap.ts below.
import { UtilsService } from '@abp/utils/utils.service';
import { CompilerOptions, NgModuleRef, Type } from '@angular/core';
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { AppAuthService } from '@app/shared/common/auth/app-auth.service';
import { AppConsts } from '@shared/AppConsts';
import { SubdomainTenancyNameFinder } from '@shared/helpers/SubdomainTenancyNameFinder';
import * as moment from 'moment';
import * as _ from 'lodash';
import { UrlHelper } from './shared/helpers/UrlHelper';
import { XmlHttpRequestHelper } from '@shared/helpers/XmlHttpRequestHelper';
import { DynamicResourcesHelper } from '@shared/helpers/DynamicResourcesHelper';
import { environment } from './environments/environment';
import { LocaleMappingService } from '@shared/locale-mapping.service';
export class AppPreBootstrap {
static run(appRootUrl: string, callback: () => void, resolve: any, reject: any): void {
AppPreBootstrap.getApplicationConfig(appRootUrl, () => {
if (UrlHelper.isInstallUrl(location.href)) {
AppPreBootstrap.loadAssetsForInstallPage(callback);
return;
}
const queryStringObj = UrlHelper.getQueryParameters();
if (queryStringObj.redirect && queryStringObj.redirect === 'TenantRegistration') {
if (queryStringObj.forceNewRegistration) {
new AppAuthService().logout();
}
location.href = AppConsts.appBaseUrl + '/account/select-edition';
} else if (queryStringObj.impersonationToken) {
AppPreBootstrap.impersonatedAuthenticate(queryStringObj.impersonationToken, queryStringObj.tenantId, () => { AppPreBootstrap.getUserConfiguration(callback); });
} else if (queryStringObj.switchAccountToken) {
AppPreBootstrap.linkedAccountAuthenticate(queryStringObj.switchAccountToken, queryStringObj.tenantId, () => { AppPreBootstrap.getUserConfiguration(callback); });
} else {
AppPreBootstrap.getUserConfiguration(callback);
}
});
}
static bootstrap<TM>(moduleType: Type<TM>, compilerOptions?: CompilerOptions | CompilerOptions[]): Promise<NgModuleRef<TM>> {
return platformBrowserDynamic().bootstrapModule(moduleType, compilerOptions);
}
private static getApplicationConfig(appRootUrl: string, callback: () => void) {
let type = 'GET';
let url = appRootUrl + 'assets/' + environment.appConfig;
let customHeaders = [
{
name: 'Abp.TenantId',
value: abp.multiTenancy.getTenantIdCookie() + ''
}];
XmlHttpRequestHelper.ajax(type, url, customHeaders, null, (result) => {
const subdomainTenancyNameFinder = new SubdomainTenancyNameFinder();
const tenancyName = subdomainTenancyNameFinder.getCurrentTenancyNameOrNull(result.appBaseUrl);
// CUSTOM ADDITION HERE
let getTenantIdURL = result.remoteServiceBaseUrl + '/api/services/app/Account/IsTenantAvailable';
let getTenantIdData = {tenancyName: tenancyName};
XmlHttpRequestHelper.ajax('POST', getTenantIdURL, null, JSON.stringify(getTenantIdData), (tenantResult) => {
abp.multiTenancy.setTenantIdCookie(tenantResult.result.tenantId);
AppConsts.appBaseUrlFormat = result.appBaseUrl;
AppConsts.remoteServiceBaseUrlFormat = result.remoteServiceBaseUrl;
AppConsts.localeMappings = result.localeMappings;
if (tenancyName == null) {
AppConsts.appBaseUrl = result.appBaseUrl.replace(AppConsts.tenancyNamePlaceHolderInUrl + '.', '');
//AppConsts.remoteServiceBaseUrl = result.remoteServiceBaseUrl.replace(AppConsts.tenancyNamePlaceHolderInUrl + '.', '');
} else {
AppConsts.appBaseUrl = result.appBaseUrl.replace(AppConsts.tenancyNamePlaceHolderInUrl, tenancyName);
//AppConsts.remoteServiceBaseUrl = result.remoteServiceBaseUrl.replace(AppConsts.tenancyNamePlaceHolderInUrl, tenancyName);
}
AppConsts.remoteServiceBaseUrl = result.remoteServiceBaseUrl;
callback();
});
});
}
private static getCurrentClockProvider(currentProviderName: string): abp.timing.IClockProvider {
if (currentProviderName === 'unspecifiedClockProvider') {
return abp.timing.unspecifiedClockProvider;
}
if (currentProviderName === 'utcClockProvider') {
return abp.timing.utcClockProvider;
}
return abp.timing.localClockProvider;
}
private static impersonatedAuthenticate(impersonationToken: string, tenantId: number, callback: () => void): void {
abp.multiTenancy.setTenantIdCookie(tenantId);
const cookieLangValue = abp.utils.getCookieValue('Abp.Localization.CultureName');
let requestHeaders = {
'.AspNetCore.Culture': ('c=' + cookieLangValue + '|uic=' + cookieLangValue),
'Abp.TenantId': abp.multiTenancy.getTenantIdCookie()
};
XmlHttpRequestHelper.ajax(
'POST',
AppConsts.remoteServiceBaseUrl + '/api/TokenAuth/ImpersonatedAuthenticate?impersonationToken=' + impersonationToken,
requestHeaders,
null,
(response) => {
let result = response.result;
abp.auth.setToken(result.accessToken);
AppPreBootstrap.setEncryptedTokenCookie(result.encryptedAccessToken);
location.search = '';
callback();
}
);
}
private static linkedAccountAuthenticate(switchAccountToken: string, tenantId: number, callback: () => void): void {
abp.multiTenancy.setTenantIdCookie(tenantId);
const cookieLangValue = abp.utils.getCookieValue('Abp.Localization.CultureName');
let requestHeaders = {
'.AspNetCore.Culture': ('c=' + cookieLangValue + '|uic=' + cookieLangValue),
'Abp.TenantId': abp.multiTenancy.getTenantIdCookie()
};
XmlHttpRequestHelper.ajax(
'POST',
AppConsts.remoteServiceBaseUrl + '/api/TokenAuth/LinkedAccountAuthenticate?switchAccountToken=' + switchAccountToken,
requestHeaders,
null,
(response) => {
let result = response.result;
abp.auth.setToken(result.accessToken);
AppPreBootstrap.setEncryptedTokenCookie(result.encryptedAccessToken);
location.search = '';
callback();
}
);
}
private static getUserConfiguration(callback: () => void): any {
const cookieLangValue = abp.utils.getCookieValue('Abp.Localization.CultureName');
const token = abp.auth.getToken();
let requestHeaders = {
'.AspNetCore.Culture': ('c=' + cookieLangValue + '|uic=' + cookieLangValue),
'Abp.TenantId': abp.multiTenancy.getTenantIdCookie(),
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Methods': 'GET,PUT,POST'
};
if (token) {
requestHeaders['Authorization'] = 'Bearer ' + token;
}
return XmlHttpRequestHelper.ajax('GET', AppConsts.remoteServiceBaseUrl + '/AbpUserConfiguration/GetAll', requestHeaders, null, (response) => {
let result = JSON.parse(response).result;
_.merge(abp, result);
abp.clock.provider = this.getCurrentClockProvider(result.clock.provider);
moment.locale(new LocaleMappingService().map('moment', abp.localization.currentLanguage.name));
(window as any).moment.locale(new LocaleMappingService().map('moment', abp.localization.currentLanguage.name));
console.log('Should be removed after yarn: ', moment);
if (abp.clock.provider.supportsMultipleTimezone) {
moment.tz.setDefault(abp.timing.timeZoneInfo.iana.timeZoneId);
(window as any).moment.tz.setDefault(abp.timing.timeZoneInfo.iana.timeZoneId);
}
abp.event.trigger('abp.dynamicScriptsInitialized');
AppConsts.recaptchaSiteKey = abp.setting.get('Recaptcha.SiteKey');
AppConsts.subscriptionExpireNootifyDayCount = parseInt(abp.setting.get('App.TenantManagement.SubscriptionExpireNotifyDayCount'));
DynamicResourcesHelper.loadResources(callback);
});
}
private static setEncryptedTokenCookie(encryptedToken: string) {
new UtilsService().setCookieValue(AppConsts.authorization.encrptedAuthTokenName,
encryptedToken,
new Date(new Date().getTime() + 365 * 86400000), //1 year
abp.appPath
);
}
private static loadAssetsForInstallPage(callback) {
abp.setting.values['App.UiManagement.Theme'] = 'default';
abp.setting.values['default.App.UiManagement.ThemeColor'] = 'default';
DynamicResourcesHelper.loadResources(callback);
}
}