mirror of https://github.com/abpframework/abp.git
Browse Source
# Conflicts: # npm/ng-packs/packages/core/src/lib/core.module.ts # npm/ng-packs/packages/core/src/lib/localization.module.ts # npm/ng-packs/packages/theme-basic/src/lib/theme-basic.module.tspull/22829/head
76 changed files with 534 additions and 160 deletions
@ -1,3 +1,52 @@ |
|||||
# Configuration |
# Configuration |
||||
|
|
||||
ASP.NET Core has an flexible and extensible key-value based configuration system. In fact, the configuration system is a part of Microsoft.Extensions libraries and it is independent from ASP.NET Core. That means it can be used in any type of application. See [Microsoft's documentation](https://docs.microsoft.com/en-us/aspnet/core/fundamentals/configuration/) to learn the configuration infrastructure. ABP is 100% compatible with the configuration system. |
ASP.NET Core has an flexible and extensible key-value based configuration system. The configuration system is a part of Microsoft.Extensions libraries and it is independent from ASP.NET Core. That means it can be used in any type of application. |
||||
|
|
||||
|
See [Microsoft's documentation](https://docs.microsoft.com/en-us/aspnet/core/fundamentals/configuration/) to learn the configuration infrastructure. ABP is 100% compatible with the configuration system. |
||||
|
|
||||
|
## Getting the Configuration |
||||
|
|
||||
|
You may need to get the `IConfiguration` service in various places in your codebase. The following section shows two common ways. |
||||
|
|
||||
|
### In Module Classes |
||||
|
|
||||
|
You typically need to get configuration while initializing your application. You can get the `IConfiguration` service using the `ServiceConfigurationContext.Configuration` property inside your [module class](../architecture/modularity/basics.md) as the following example: |
||||
|
|
||||
|
````csharp |
||||
|
public class MyAppModule : AbpModule |
||||
|
{ |
||||
|
public override void ConfigureServices(ServiceConfigurationContext context) |
||||
|
{ |
||||
|
var connectionString = context.Configuration["ConnectionStrings:Default"]; |
||||
|
} |
||||
|
} |
||||
|
```` |
||||
|
|
||||
|
`context.Configuration` is a shortcut property for the `context.Services.GetConfiguration()` method. In general, prefer using `context.Configuration` for simplicity and readability when working within module classes. Use `context.Services.GetConfiguration()` in other contexts where you have an `IServiceCollection` object but do not have access to the `context.Configuration` property. (`IServiceCollection.GetConfiguration` is an extension method that can be used whenever you have an `IServiceCollection` object). |
||||
|
|
||||
|
### In Your Services |
||||
|
|
||||
|
You can directly [inject](dependency-injection.md) the `IConfiguration` service into your services: |
||||
|
|
||||
|
````csharp |
||||
|
public class MyService : ITransientDependency |
||||
|
{ |
||||
|
private readonly IConfiguration _configuration; |
||||
|
|
||||
|
public MyService(IConfiguration configuration) |
||||
|
{ |
||||
|
_configuration = configuration; |
||||
|
} |
||||
|
|
||||
|
public string? GetConnectionString() |
||||
|
{ |
||||
|
return _configuration["ConnectionStrings:Default"]; |
||||
|
} |
||||
|
} |
||||
|
```` |
||||
|
|
||||
|
## See Also |
||||
|
|
||||
|
* [Microsoft's Configuration Documentation](https://docs.microsoft.com/en-us/aspnet/core/fundamentals/configuration/) |
||||
|
* [The Options Pattern](options.md) |
||||
|
|
||||
|
|||||
|
After Width: | Height: | Size: 3.1 KiB |
@ -1 +1,2 @@ |
|||||
export * from './api.interceptor'; |
export * from './api.interceptor'; |
||||
|
export * from './timezone.interceptor'; |
||||
|
|||||
@ -0,0 +1,26 @@ |
|||||
|
import { inject, Injectable } from '@angular/core'; |
||||
|
import { HttpEvent, HttpHandler, HttpInterceptor, HttpRequest } from '@angular/common/http'; |
||||
|
import { TimezoneService } from '../services'; |
||||
|
import { Observable } from 'rxjs'; |
||||
|
|
||||
|
@Injectable({ |
||||
|
providedIn: 'root', |
||||
|
}) |
||||
|
export class TimezoneInterceptor implements HttpInterceptor { |
||||
|
protected readonly timezoneService = inject(TimezoneService); |
||||
|
|
||||
|
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> { |
||||
|
if (!this.timezoneService.isUtcClockEnabled) { |
||||
|
return next.handle(req); |
||||
|
} |
||||
|
const timezone = this.timezoneService.timezone; |
||||
|
if (timezone) { |
||||
|
req = req.clone({ |
||||
|
setHeaders: { |
||||
|
__timezone: timezone, |
||||
|
}, |
||||
|
}); |
||||
|
} |
||||
|
return next.handle(req); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,41 @@ |
|||||
|
import { inject, Injectable, Pipe, PipeTransform } from '@angular/core'; |
||||
|
import { |
||||
|
Observable, |
||||
|
of, |
||||
|
filter, |
||||
|
take, |
||||
|
switchMap, |
||||
|
map, |
||||
|
startWith, |
||||
|
distinctUntilChanged, |
||||
|
} from 'rxjs'; |
||||
|
import { ConfigStateService, LocalizationService } from '../services'; |
||||
|
|
||||
|
@Injectable() |
||||
|
@Pipe({ |
||||
|
name: 'abpLazyLocalization', |
||||
|
}) |
||||
|
export class LazyLocalizationPipe implements PipeTransform { |
||||
|
private localizationService = inject(LocalizationService); |
||||
|
private configStateService = inject(ConfigStateService); |
||||
|
|
||||
|
transform(key: string, ...params: (string | string[])[]): Observable<string> { |
||||
|
if (!key) { |
||||
|
return of(''); |
||||
|
} |
||||
|
|
||||
|
const flatParams = params.reduce<string[]>( |
||||
|
(acc, val) => (Array.isArray(val) ? acc.concat(val) : [...acc, val]), |
||||
|
[], |
||||
|
); |
||||
|
|
||||
|
return this.configStateService.getAll$().pipe( |
||||
|
filter(config => !!config.localization), |
||||
|
take(1), |
||||
|
switchMap(() => this.localizationService.get(key, ...flatParams)), |
||||
|
map(translation => (translation && translation !== key ? translation : '')), |
||||
|
startWith(''), |
||||
|
distinctUntilChanged(), |
||||
|
); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,50 @@ |
|||||
|
import { Pipe, PipeTransform, Injectable, inject, LOCALE_ID } from '@angular/core'; |
||||
|
import { ConfigStateService, LocalizationService, TimeService, TimezoneService } from '../services'; |
||||
|
import { getShortDateFormat, getShortDateShortTimeFormat, getShortTimeFormat } from '../utils'; |
||||
|
|
||||
|
@Injectable() |
||||
|
@Pipe({ |
||||
|
name: 'abpUtcToLocal', |
||||
|
}) |
||||
|
export class UtcToLocalPipe implements PipeTransform { |
||||
|
protected readonly timezoneService = inject(TimezoneService); |
||||
|
protected readonly timeService = inject(TimeService); |
||||
|
protected readonly configState = inject(ConfigStateService); |
||||
|
protected readonly localizationService = inject(LocalizationService); |
||||
|
protected readonly locale = inject(LOCALE_ID); |
||||
|
|
||||
|
transform( |
||||
|
value: string | Date | null | undefined, |
||||
|
type: 'date' | 'datetime' | 'time', |
||||
|
): string | Date { |
||||
|
if (!value) return ''; |
||||
|
|
||||
|
const date = new Date(value); |
||||
|
if (isNaN(date.getTime())) return ''; |
||||
|
|
||||
|
const format = this.getFormat(type); |
||||
|
|
||||
|
try { |
||||
|
if (this.timezoneService.isUtcClockEnabled) { |
||||
|
const timeZone = this.timezoneService.timezone; |
||||
|
return this.timeService.formatDateWithStandardOffset(date, format, timeZone); |
||||
|
} else { |
||||
|
return this.timeService.formatWithoutTimeZone(date, format); |
||||
|
} |
||||
|
} catch (err) { |
||||
|
return value; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private getFormat(propType: 'date' | 'datetime' | 'time'): string { |
||||
|
switch (propType) { |
||||
|
case 'date': |
||||
|
return getShortDateFormat(this.configState); |
||||
|
case 'time': |
||||
|
return getShortTimeFormat(this.configState); |
||||
|
case 'datetime': |
||||
|
default: |
||||
|
return getShortDateShortTimeFormat(this.configState); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,107 @@ |
|||||
|
import { inject, Injectable, LOCALE_ID } from '@angular/core'; |
||||
|
import { DateTime } from 'luxon'; |
||||
|
|
||||
|
@Injectable({ |
||||
|
providedIn: 'root', |
||||
|
}) |
||||
|
export class TimeService { |
||||
|
private locale = inject(LOCALE_ID); |
||||
|
|
||||
|
/** |
||||
|
* Returns the current date and time in the specified timezone. |
||||
|
* |
||||
|
* @param zone - An IANA timezone name (e.g., 'Europe/Istanbul', 'UTC'); defaults to the system's local timezone. |
||||
|
* @returns A Luxon DateTime instance representing the current time in the given timezone. |
||||
|
*/ |
||||
|
now(zone = 'local'): DateTime { |
||||
|
return DateTime.now().setZone(zone); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Converts the input date to the specified timezone, applying any timezone and daylight saving time (DST) adjustments. |
||||
|
* |
||||
|
* This method: |
||||
|
* 1. Parses the input value into a Luxon DateTime object. |
||||
|
* 2. Applies the specified IANA timezone, including any DST shifts based on the given date. |
||||
|
* |
||||
|
* @param value - The ISO string or Date object to convert. |
||||
|
* @param zone - An IANA timezone name (e.g., 'America/New_York'). |
||||
|
* @returns A Luxon DateTime instance adjusted to the specified timezone and DST rules. |
||||
|
*/ |
||||
|
toZone(value: string | Date, zone: string): DateTime { |
||||
|
return DateTime.fromISO(value instanceof Date ? value.toISOString() : value, { |
||||
|
zone, |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Formats the input date by applying timezone and daylight saving time (DST) adjustments. |
||||
|
* |
||||
|
* This method: |
||||
|
* 1. Converts the input date to the specified timezone. |
||||
|
* 2. Formats the result using the given format and locale, reflecting any timezone or DST shifts. |
||||
|
* |
||||
|
* @param value - The ISO string or Date object to format. |
||||
|
* @param format - The format string (default: 'ff'). |
||||
|
* @param zone - Optional IANA timezone name (e.g., 'America/New_York'); defaults to the system's local timezone. |
||||
|
* @returns A formatted date string adjusted for the given timezone and DST rules. |
||||
|
*/ |
||||
|
format(value: string | Date, format = 'ff', zone = 'local'): string { |
||||
|
return this.toZone(value, zone).setLocale(this.locale).toFormat(format); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Formats a date using the standard time offset (ignoring daylight saving time) for the specified timezone. |
||||
|
* |
||||
|
* This method: |
||||
|
* 1. Converts the input date to UTC. |
||||
|
* 2. Calculates the standard UTC offset for the given timezone (based on January 1st to avoid DST). |
||||
|
* 3. Applies the standard offset manually to the UTC time. |
||||
|
* 4. Formats the result using the specified format and locale, without applying additional timezone shifts. |
||||
|
* |
||||
|
* @param value - The ISO string or Date object to format. |
||||
|
* @param format - The Luxon format string (default: 'ff'). |
||||
|
* @param zone - Optional IANA timezone name (e.g., 'America/New_York'); if omitted, system local timezone is used. |
||||
|
* @returns A formatted date string adjusted by standard time (non-DST). |
||||
|
*/ |
||||
|
formatDateWithStandardOffset(value: string | Date, format = 'ff', zone?: string): string { |
||||
|
const utcDate = |
||||
|
typeof value === 'string' |
||||
|
? DateTime.fromISO(value, { zone: 'UTC' }) |
||||
|
: DateTime.fromJSDate(value, { zone: 'UTC' }); |
||||
|
|
||||
|
if (!utcDate.isValid) return ''; |
||||
|
|
||||
|
const targetZone = zone ?? DateTime.local().zoneName; |
||||
|
|
||||
|
const januaryDate = DateTime.fromObject( |
||||
|
{ year: utcDate.year, month: 1, day: 1 }, |
||||
|
{ zone: targetZone }, |
||||
|
); |
||||
|
const standardOffset = januaryDate.offset; |
||||
|
const dateWithStandardOffset = utcDate.plus({ minutes: standardOffset }); |
||||
|
|
||||
|
return dateWithStandardOffset.setZone('UTC').setLocale(this.locale).toFormat(format); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Formats the input date using its original clock time, without converting based on timezone or DST |
||||
|
* |
||||
|
* This method: |
||||
|
* 1. Converts the input date to ISO string. |
||||
|
* 2. Calculates the date time in UTC, keeping the local time. |
||||
|
* 3. Formats the result using the specified format and locale, without shifting timezones. |
||||
|
* |
||||
|
* @param value - The ISO string or Date object to format. |
||||
|
* @param format - The format string (default: 'ff'). |
||||
|
* @returns A formatted date string without applying timezone. |
||||
|
*/ |
||||
|
formatWithoutTimeZone(value: string | Date, format = 'ff'): string { |
||||
|
const isoString = value instanceof Date ? value.toISOString() : value; |
||||
|
|
||||
|
const dateTime = DateTime.fromISO(isoString) |
||||
|
.setZone('utc', { keepLocalTime: true }) |
||||
|
.setLocale(this.locale); |
||||
|
return dateTime.toFormat(format); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,63 @@ |
|||||
|
import { inject, Injectable } from '@angular/core'; |
||||
|
import { DOCUMENT } from '@angular/common'; |
||||
|
import { ConfigStateService } from './config-state.service'; |
||||
|
|
||||
|
@Injectable({ |
||||
|
providedIn: 'root', |
||||
|
}) |
||||
|
export class TimezoneService { |
||||
|
protected readonly configState = inject(ConfigStateService); |
||||
|
protected readonly document = inject(DOCUMENT); |
||||
|
private readonly cookieKey = '__timezone'; |
||||
|
private timeZoneNameFromSettings: string | null | undefined; |
||||
|
public isUtcClockEnabled: boolean | undefined; |
||||
|
|
||||
|
constructor() { |
||||
|
this.configState.getOne$('setting').subscribe(settings => { |
||||
|
this.timeZoneNameFromSettings = settings?.values?.['Abp.Timing.TimeZone']; |
||||
|
}); |
||||
|
this.configState.getOne$('clock').subscribe(clock => { |
||||
|
this.isUtcClockEnabled = clock?.kind === 'Utc'; |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Returns the effective timezone to be used across the application. |
||||
|
* |
||||
|
* This value is determined based on the clock kind setting in the configuration: |
||||
|
* - If clock kind is not equal to Utc, the browser's local timezone is returned. |
||||
|
* - If clock kind is equal to Utc, the configured timezone (`timeZoneNameFromSettings`) is returned if available; |
||||
|
* otherwise, the browser's timezone is used as a fallback. |
||||
|
* |
||||
|
* @returns The IANA timezone name (e.g., 'Europe/Istanbul', 'America/New_York'). |
||||
|
*/ |
||||
|
get timezone(): string { |
||||
|
if (!this.isUtcClockEnabled) { |
||||
|
return this.getBrowserTimezone(); |
||||
|
} |
||||
|
return this.timeZoneNameFromSettings || this.getBrowserTimezone(); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Retrieves the browser's local timezone based on the user's system settings. |
||||
|
* |
||||
|
* @returns The IANA timezone name (e.g., 'Europe/Istanbul', 'America/New_York'). |
||||
|
*/ |
||||
|
getBrowserTimezone(): string { |
||||
|
return Intl.DateTimeFormat().resolvedOptions().timeZone; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Sets the application's timezone in a cookie to persist the user's selected timezone. |
||||
|
* |
||||
|
* This method sets the cookie only if the clock kind setting is set to UTC. |
||||
|
* The cookie is stored using the key defined by `this.cookieKey` and applied to the root path (`/`). |
||||
|
* |
||||
|
* @param timezone - The IANA timezone name to be stored (e.g., 'Europe/Istanbul'). |
||||
|
*/ |
||||
|
setTimezone(timezone: string): void { |
||||
|
if (this.isUtcClockEnabled) { |
||||
|
this.document.cookie = `${this.cookieKey}=${timezone}; path=/`; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -1 +0,0 @@ |
|||||
export * from './lazy-translate.pipe'; |
|
||||
@ -1,20 +0,0 @@ |
|||||
import { LocalizationService, ConfigStateService } from '@abp/ng.core'; |
|
||||
import { inject, Pipe, PipeTransform } from '@angular/core'; |
|
||||
import { Observable, filter, take, switchMap, shareReplay } from 'rxjs'; |
|
||||
|
|
||||
@Pipe({ |
|
||||
name: 'abpLazyTranslate', |
|
||||
}) |
|
||||
export class LazyTranslatePipe implements PipeTransform { |
|
||||
private localizationService = inject(LocalizationService); |
|
||||
private configStateService = inject(ConfigStateService); |
|
||||
|
|
||||
transform(key: string): Observable<string> { |
|
||||
return this.configStateService.getAll$().pipe( |
|
||||
filter(config => !!config.localization), |
|
||||
take(1), |
|
||||
switchMap(() => this.localizationService.get(key)), |
|
||||
shareReplay({ bufferSize: 1, refCount: true }), |
|
||||
); |
|
||||
} |
|
||||
} |
|
||||
Loading…
Reference in new issue