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,
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.
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);
}
}
how should i override to use the browser datetime date format instead of this .. moment.locale(new LocaleMappingService().map('moment', abp.localization.currentLanguage.name)); (window as any).moment.locale(new LocaleMappingService().map('moment', abp.localization.currentLanguage.name));
yes application is using ClockProviders.Utc . We are not asking the user which timezone they live in when users are created and hence we are not setting that in the table. But by default the user's datetime format on all the grids used in the application should be based on their browser datetime format. Can you please guide me with a sample code and what needs to be done on the api side and client side ? I tried using the logic in Appprebootstrap.ts but does not seem to work.
Thank you for the response maliming. Do you mean it's not using ISingletonDependency because ISingletonDependency is more error prone, like concurrency conflicts and memory lead, for a background job?
found the issue:
In aspnetboilerplate/src/Abp/EntityHistory/EntityHistoryHelperBase.cs, it's missing the part checking against EntityHistorySelector:
if (EntityHistoryConfiguration.Selectors.Any(selector => selector.Predicate(entityType)))
{
return true;
}
Thank you,
Thank you maliming! But doesn't that mean the Entity Change Capture thing isn't working as expected?