diff --git a/samples/Mvc.Server/Controllers/AuthorizationController.cs b/samples/Mvc.Server/Controllers/AuthorizationController.cs
index 2b9d07c0..775664cb 100644
--- a/samples/Mvc.Server/Controllers/AuthorizationController.cs
+++ b/samples/Mvc.Server/Controllers/AuthorizationController.cs
@@ -216,7 +216,7 @@ namespace Mvc.Server
// In every other case, render the consent form.
default: return View(new AuthorizeViewModel
{
- ApplicationName = await _applicationManager.GetDisplayNameAsync(application),
+ ApplicationName = await _applicationManager.GetLocalizedDisplayNameAsync(application),
Scope = request.Scope
});
}
@@ -325,7 +325,7 @@ namespace Mvc.Server
// Render a form asking the user to confirm the authorization demand.
return View(new VerifyViewModel
{
- ApplicationName = await _applicationManager.GetDisplayNameAsync(application),
+ ApplicationName = await _applicationManager.GetLocalizedDisplayNameAsync(application),
Scope = string.Join(" ", result.Principal.GetScopes()),
UserCode = request.UserCode
});
diff --git a/src/OpenIddict.Abstractions/Descriptors/OpenIddictApplicationDescriptor.cs b/src/OpenIddict.Abstractions/Descriptors/OpenIddictApplicationDescriptor.cs
index 2ad8aad2..280ee25d 100644
--- a/src/OpenIddict.Abstractions/Descriptors/OpenIddictApplicationDescriptor.cs
+++ b/src/OpenIddict.Abstractions/Descriptors/OpenIddictApplicationDescriptor.cs
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
+using System.Globalization;
namespace OpenIddict.Abstractions
{
@@ -25,7 +26,7 @@ namespace OpenIddict.Abstractions
/// Gets or sets the consent type
/// associated with the application.
///
- public virtual string ConsentType { get; set; }
+ public string ConsentType { get; set; }
///
/// Gets or sets the display name
@@ -33,6 +34,12 @@ namespace OpenIddict.Abstractions
///
public string DisplayName { get; set; }
+ ///
+ /// Gets the localized display names associated with the application.
+ ///
+ public Dictionary DisplayNames { get; }
+ = new Dictionary();
+
///
/// Gets the permissions associated with the application.
///
diff --git a/src/OpenIddict.Abstractions/Descriptors/OpenIddictAuthorizationDescriptor.cs b/src/OpenIddict.Abstractions/Descriptors/OpenIddictAuthorizationDescriptor.cs
index 5f5029d6..13915489 100644
--- a/src/OpenIddict.Abstractions/Descriptors/OpenIddictAuthorizationDescriptor.cs
+++ b/src/OpenIddict.Abstractions/Descriptors/OpenIddictAuthorizationDescriptor.cs
@@ -38,6 +38,6 @@ namespace OpenIddict.Abstractions
///
/// Gets or sets the type of the authorization.
///
- public virtual string Type { get; set; }
+ public string Type { get; set; }
}
}
diff --git a/src/OpenIddict.Abstractions/Descriptors/OpenIddictScopeDescriptor.cs b/src/OpenIddict.Abstractions/Descriptors/OpenIddictScopeDescriptor.cs
index 24433411..5329a81e 100644
--- a/src/OpenIddict.Abstractions/Descriptors/OpenIddictScopeDescriptor.cs
+++ b/src/OpenIddict.Abstractions/Descriptors/OpenIddictScopeDescriptor.cs
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
+using System.Globalization;
namespace OpenIddict.Abstractions
{
@@ -12,23 +13,33 @@ namespace OpenIddict.Abstractions
/// Gets or sets the description
/// associated with the scope.
///
- public virtual string Description { get; set; }
+ public string Description { get; set; }
+
+ ///
+ /// Gets the localized descriptions associated with the scope.
+ ///
+ public Dictionary Descriptions { get; } = new Dictionary();
///
/// Gets or sets the display name
/// associated with the scope.
///
- public virtual string DisplayName { get; set; }
+ public string DisplayName { get; set; }
+
+ ///
+ /// Gets the localized display names associated with the scope.
+ ///
+ public Dictionary DisplayNames { get; } = new Dictionary();
///
/// Gets or sets the unique name
/// associated with the scope.
///
- public virtual string Name { get; set; }
+ public string Name { get; set; }
///
/// Gets the resources associated with the scope.
///
- public virtual HashSet Resources { get; } = new HashSet(StringComparer.Ordinal);
+ public HashSet Resources { get; } = new HashSet(StringComparer.Ordinal);
}
}
diff --git a/src/OpenIddict.Abstractions/Managers/IOpenIddictApplicationManager.cs b/src/OpenIddict.Abstractions/Managers/IOpenIddictApplicationManager.cs
index 7e3b99e6..a518a4b4 100644
--- a/src/OpenIddict.Abstractions/Managers/IOpenIddictApplicationManager.cs
+++ b/src/OpenIddict.Abstractions/Managers/IOpenIddictApplicationManager.cs
@@ -2,6 +2,7 @@
using System.Collections.Generic;
using System.Collections.Immutable;
using System.ComponentModel.DataAnnotations;
+using System.Globalization;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
@@ -198,6 +199,17 @@ namespace OpenIddict.Abstractions
///
ValueTask GetDisplayNameAsync([NotNull] object application, CancellationToken cancellationToken = default);
+ ///
+ /// Retrieves the localized display names associated with an application.
+ ///
+ /// The application.
+ /// The that can be used to abort the operation.
+ ///
+ /// A that can be used to monitor the asynchronous operation,
+ /// whose result returns all the localized display names associated with the application.
+ ///
+ ValueTask> GetDisplayNamesAsync([NotNull] object application, CancellationToken cancellationToken = default);
+
///
/// Retrieves the unique identifier associated with an application.
///
@@ -209,6 +221,33 @@ namespace OpenIddict.Abstractions
///
ValueTask GetIdAsync([NotNull] object application, CancellationToken cancellationToken = default);
+ ///
+ /// Retrieves the localized display name associated with an application
+ /// and corresponding to the current UI culture or one of its parents.
+ /// If no matching value can be found, the non-localized value is returned.
+ ///
+ /// The application.
+ /// The that can be used to abort the operation.
+ ///
+ /// A that can be used to monitor the asynchronous operation,
+ /// whose result returns the matching localized display name associated with the application.
+ ///
+ ValueTask GetLocalizedDisplayNameAsync([NotNull] object application, CancellationToken cancellationToken = default);
+
+ ///
+ /// Retrieves the localized display name associated with an application
+ /// and corresponding to the specified culture or one of its parents.
+ /// If no matching value can be found, the non-localized value is returned.
+ ///
+ /// The application.
+ /// The culture (typically ).
+ /// The that can be used to abort the operation.
+ ///
+ /// A that can be used to monitor the asynchronous operation,
+ /// whose result returns the matching localized display name associated with the application.
+ ///
+ ValueTask GetLocalizedDisplayNameAsync([NotNull] object application, [NotNull] CultureInfo culture, CancellationToken cancellationToken = default);
+
///
/// Retrieves the permissions associated with an application.
///
diff --git a/src/OpenIddict.Abstractions/Managers/IOpenIddictScopeManager.cs b/src/OpenIddict.Abstractions/Managers/IOpenIddictScopeManager.cs
index 7f6f8b03..eec7d0fb 100644
--- a/src/OpenIddict.Abstractions/Managers/IOpenIddictScopeManager.cs
+++ b/src/OpenIddict.Abstractions/Managers/IOpenIddictScopeManager.cs
@@ -2,6 +2,7 @@
using System.Collections.Generic;
using System.Collections.Immutable;
using System.ComponentModel.DataAnnotations;
+using System.Globalization;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
@@ -149,6 +150,17 @@ namespace OpenIddict.Abstractions
///
ValueTask GetDescriptionAsync([NotNull] object scope, CancellationToken cancellationToken = default);
+ ///
+ /// Retrieves the localized descriptions associated with an scope.
+ ///
+ /// The scope.
+ /// The that can be used to abort the operation.
+ ///
+ /// A that can be used to monitor the asynchronous operation,
+ /// whose result returns all the localized descriptions associated with the scope.
+ ///
+ ValueTask> GetDescriptionsAsync([NotNull] object scope, CancellationToken cancellationToken = default);
+
///
/// Retrieves the display name associated with a scope.
///
@@ -160,6 +172,17 @@ namespace OpenIddict.Abstractions
///
ValueTask GetDisplayNameAsync([NotNull] object scope, CancellationToken cancellationToken = default);
+ ///
+ /// Retrieves the localized display names associated with an scope.
+ ///
+ /// The scope.
+ /// The that can be used to abort the operation.
+ ///
+ /// A that can be used to monitor the asynchronous operation,
+ /// whose result returns all the localized display names associated with the scope.
+ ///
+ ValueTask> GetDisplayNamesAsync([NotNull] object scope, CancellationToken cancellationToken = default);
+
///
/// Retrieves the unique identifier associated with a scope.
///
@@ -171,6 +194,60 @@ namespace OpenIddict.Abstractions
///
ValueTask GetIdAsync([NotNull] object scope, CancellationToken cancellationToken = default);
+ ///
+ /// Retrieves the localized description associated with an scope
+ /// and corresponding to the current UI culture or one of its parents.
+ /// If no matching value can be found, the non-localized value is returned.
+ ///
+ /// The scope.
+ /// The that can be used to abort the operation.
+ ///
+ /// A that can be used to monitor the asynchronous operation,
+ /// whose result returns the matching localized description associated with the scope.
+ ///
+ ValueTask GetLocalizedDescriptionAsync([NotNull] object scope, CancellationToken cancellationToken = default);
+
+ ///
+ /// Retrieves the localized description associated with an scope
+ /// and corresponding to the specified culture or one of its parents.
+ /// If no matching value can be found, the non-localized value is returned.
+ ///
+ /// The scope.
+ /// The culture (typically ).
+ /// The that can be used to abort the operation.
+ ///
+ /// A that can be used to monitor the asynchronous operation,
+ /// whose result returns the matching localized description associated with the scope.
+ ///
+ ValueTask GetLocalizedDescriptionAsync([NotNull] object scope, [NotNull] CultureInfo culture, CancellationToken cancellationToken = default);
+
+ ///
+ /// Retrieves the localized display name associated with an scope
+ /// and corresponding to the current UI culture or one of its parents.
+ /// If no matching value can be found, the non-localized value is returned.
+ ///
+ /// The scope.
+ /// The that can be used to abort the operation.
+ ///
+ /// A that can be used to monitor the asynchronous operation,
+ /// whose result returns the display name associated with the scope.
+ ///
+ ValueTask GetLocalizedDisplayNameAsync([NotNull] object scope, CancellationToken cancellationToken = default);
+
+ ///
+ /// Retrieves the localized display name associated with an scope
+ /// and corresponding to the specified culture or one of its parents.
+ /// If no matching value can be found, the non-localized value is returned.
+ ///
+ /// The scope.
+ /// The culture (typically ).
+ /// The that can be used to abort the operation.
+ ///
+ /// A that can be used to monitor the asynchronous operation,
+ /// whose result returns the display name associated with the scope.
+ ///
+ ValueTask GetLocalizedDisplayNameAsync([NotNull] object scope, [NotNull] CultureInfo culture, CancellationToken cancellationToken = default);
+
///
/// Retrieves the name associated with a scope.
///
diff --git a/src/OpenIddict.Abstractions/Primitives/OpenIddictParameter.cs b/src/OpenIddict.Abstractions/Primitives/OpenIddictParameter.cs
index 470b1b42..7f976d25 100644
--- a/src/OpenIddict.Abstractions/Primitives/OpenIddictParameter.cs
+++ b/src/OpenIddict.Abstractions/Primitives/OpenIddictParameter.cs
@@ -374,7 +374,7 @@ namespace OpenIddict.Abstractions
return parameters;
}
- return ImmutableDictionary.Create();
+ return ImmutableDictionary.Create(StringComparer.Ordinal);
}
///
diff --git a/src/OpenIddict.Abstractions/Stores/IOpenIddictApplicationStore.cs b/src/OpenIddict.Abstractions/Stores/IOpenIddictApplicationStore.cs
index 96264937..4f77cd95 100644
--- a/src/OpenIddict.Abstractions/Stores/IOpenIddictApplicationStore.cs
+++ b/src/OpenIddict.Abstractions/Stores/IOpenIddictApplicationStore.cs
@@ -12,6 +12,7 @@ using System.Threading;
using System.Threading.Tasks;
using JetBrains.Annotations;
using System.Text.Json;
+using System.Globalization;
namespace OpenIddict.Abstractions
{
@@ -170,6 +171,17 @@ namespace OpenIddict.Abstractions
///
ValueTask GetDisplayNameAsync([NotNull] TApplication application, CancellationToken cancellationToken);
+ ///
+ /// Retrieves the localized display names associated with an application.
+ ///
+ /// The application.
+ /// The that can be used to abort the operation.
+ ///
+ /// A that can be used to monitor the asynchronous operation,
+ /// whose result returns all the localized display names associated with the application.
+ ///
+ ValueTask> GetDisplayNamesAsync([NotNull] TApplication application, CancellationToken cancellationToken);
+
///
/// Retrieves the unique identifier associated with an application.
///
@@ -315,6 +327,16 @@ namespace OpenIddict.Abstractions
/// A that can be used to monitor the asynchronous operation.
ValueTask SetDisplayNameAsync([NotNull] TApplication application, [CanBeNull] string name, CancellationToken cancellationToken);
+ ///
+ /// Sets the localized display names associated with an application.
+ ///
+ /// The application.
+ /// The localized display names associated with the application.
+ /// The that can be used to abort the operation.
+ /// A that can be used to monitor the asynchronous operation.
+ ValueTask SetDisplayNamesAsync([NotNull] TApplication application,
+ [CanBeNull] ImmutableDictionary names, CancellationToken cancellationToken);
+
///
/// Sets the permissions associated with an application.
///
diff --git a/src/OpenIddict.Abstractions/Stores/IOpenIddictScopeStore.cs b/src/OpenIddict.Abstractions/Stores/IOpenIddictScopeStore.cs
index 6ea1eb31..e41c9f71 100644
--- a/src/OpenIddict.Abstractions/Stores/IOpenIddictScopeStore.cs
+++ b/src/OpenIddict.Abstractions/Stores/IOpenIddictScopeStore.cs
@@ -12,6 +12,7 @@ using System.Threading;
using System.Threading.Tasks;
using JetBrains.Annotations;
using System.Text.Json;
+using System.Globalization;
namespace OpenIddict.Abstractions
{
@@ -124,6 +125,17 @@ namespace OpenIddict.Abstractions
///
ValueTask GetDescriptionAsync([NotNull] TScope scope, CancellationToken cancellationToken);
+ ///
+ /// Retrieves the localized descriptions associated with a scope.
+ ///
+ /// The scope.
+ /// The that can be used to abort the operation.
+ ///
+ /// A that can be used to monitor the asynchronous operation,
+ /// whose result returns all the localized descriptions associated with the specified scope.
+ ///
+ ValueTask> GetDescriptionsAsync([NotNull] TScope scope, CancellationToken cancellationToken);
+
///
/// Retrieves the display name associated with a scope.
///
@@ -135,6 +147,17 @@ namespace OpenIddict.Abstractions
///
ValueTask GetDisplayNameAsync([NotNull] TScope scope, CancellationToken cancellationToken);
+ ///
+ /// Retrieves the localized display names associated with a scope.
+ ///
+ /// The scope.
+ /// The that can be used to abort the operation.
+ ///
+ /// A that can be used to monitor the asynchronous operation,
+ /// whose result returns all the localized display names associated with the scope.
+ ///
+ ValueTask> GetDisplayNamesAsync([NotNull] TScope scope, CancellationToken cancellationToken);
+
///
/// Retrieves the unique identifier associated with a scope.
///
@@ -220,6 +243,16 @@ namespace OpenIddict.Abstractions
/// A that can be used to monitor the asynchronous operation.
ValueTask SetDescriptionAsync([NotNull] TScope scope, [CanBeNull] string description, CancellationToken cancellationToken);
+ ///
+ /// Sets the localized descriptions associated with a scope.
+ ///
+ /// The scope.
+ /// The localized descriptions associated with the authorization.
+ /// The that can be used to abort the operation.
+ /// A that can be used to monitor the asynchronous operation.
+ ValueTask SetDescriptionsAsync([NotNull] TScope scope,
+ [CanBeNull] ImmutableDictionary descriptions, CancellationToken cancellationToken);
+
///
/// Sets the display name associated with a scope.
///
@@ -229,6 +262,16 @@ namespace OpenIddict.Abstractions
/// A that can be used to monitor the asynchronous operation.
ValueTask SetDisplayNameAsync([NotNull] TScope scope, [CanBeNull] string name, CancellationToken cancellationToken);
+ ///
+ /// Sets the localized display names associated with a scope.
+ ///
+ /// The scope.
+ /// The localized display names associated with the scope.
+ /// The that can be used to abort the operation.
+ /// A that can be used to monitor the asynchronous operation.
+ ValueTask SetDisplayNamesAsync([NotNull] TScope scope,
+ [CanBeNull] ImmutableDictionary names, CancellationToken cancellationToken);
+
///
/// Sets the name associated with a scope.
///
diff --git a/src/OpenIddict.Core/Managers/OpenIddictApplicationManager.cs b/src/OpenIddict.Core/Managers/OpenIddictApplicationManager.cs
index a0f345b0..2af8a372 100644
--- a/src/OpenIddict.Core/Managers/OpenIddictApplicationManager.cs
+++ b/src/OpenIddict.Core/Managers/OpenIddictApplicationManager.cs
@@ -9,6 +9,7 @@ using System.Buffers.Binary;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.ComponentModel.DataAnnotations;
+using System.Globalization;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Security.Cryptography;
@@ -551,6 +552,32 @@ namespace OpenIddict.Core
return Store.GetDisplayNameAsync(application, cancellationToken);
}
+ ///
+ /// Retrieves the localized display names associated with an application.
+ ///
+ /// The application.
+ /// The that can be used to abort the operation.
+ ///
+ /// A that can be used to monitor the asynchronous operation,
+ /// whose result returns all the localized display names associated with the application.
+ ///
+ public virtual async ValueTask> GetDisplayNamesAsync(
+ [NotNull] TApplication application, CancellationToken cancellationToken = default)
+ {
+ if (application == null)
+ {
+ throw new ArgumentNullException(nameof(application));
+ }
+
+ var names = await Store.GetDisplayNamesAsync(application, cancellationToken);
+ if (names == null || names.Count == 0)
+ {
+ return ImmutableDictionary.Create();
+ }
+
+ return names;
+ }
+
///
/// Retrieves the unique identifier associated with an application.
///
@@ -570,6 +597,67 @@ namespace OpenIddict.Core
return Store.GetIdAsync(application, cancellationToken);
}
+ ///
+ /// Retrieves the localized display name associated with an application
+ /// and corresponding to the current UI culture or one of its parents.
+ /// If no matching value can be found, the non-localized value is returned.
+ ///
+ /// The application.
+ /// The that can be used to abort the operation.
+ ///
+ /// A that can be used to monitor the asynchronous operation,
+ /// whose result returns the matching localized display name associated with the application.
+ ///
+ public virtual ValueTask GetLocalizedDisplayNameAsync(
+ [NotNull] TApplication application, CancellationToken cancellationToken = default)
+ => GetLocalizedDisplayNameAsync(application, CultureInfo.CurrentUICulture, cancellationToken);
+
+ ///
+ /// Retrieves the localized display name associated with an application
+ /// and corresponding to the specified culture or one of its parents.
+ /// If no matching value can be found, the non-localized value is returned.
+ ///
+ /// The application.
+ /// The culture (typically ).
+ /// The that can be used to abort the operation.
+ ///
+ /// A that can be used to monitor the asynchronous operation,
+ /// whose result returns the matching localized display name associated with the application.
+ ///
+ public virtual async ValueTask GetLocalizedDisplayNameAsync(
+ [NotNull] TApplication application, [NotNull] CultureInfo culture, CancellationToken cancellationToken = default)
+ {
+ if (application == null)
+ {
+ throw new ArgumentNullException(nameof(application));
+ }
+
+ if (culture == null)
+ {
+ throw new ArgumentNullException(nameof(culture));
+ }
+
+ var names = await Store.GetDisplayNamesAsync(application, cancellationToken);
+ if (names == null || names.IsEmpty)
+ {
+ return await Store.GetDisplayNameAsync(application, cancellationToken);
+ }
+
+ do
+ {
+ if (names.TryGetValue(culture, out var name))
+ {
+ return name;
+ }
+
+ culture = culture.Parent;
+ }
+
+ while (culture != CultureInfo.InvariantCulture);
+
+ return await Store.GetDisplayNameAsync(application, cancellationToken);
+ }
+
///
/// Retrieves the permissions associated with an application.
///
@@ -819,12 +907,13 @@ namespace OpenIddict.Core
await Store.SetClientTypeAsync(application, descriptor.Type, cancellationToken);
await Store.SetConsentTypeAsync(application, descriptor.ConsentType, cancellationToken);
await Store.SetDisplayNameAsync(application, descriptor.DisplayName, cancellationToken);
- await Store.SetPermissionsAsync(application, ImmutableArray.CreateRange(descriptor.Permissions), cancellationToken);
+ await Store.SetDisplayNamesAsync(application, descriptor.DisplayNames.ToImmutableDictionary(), cancellationToken);
+ await Store.SetPermissionsAsync(application, descriptor.Permissions.ToImmutableArray(), cancellationToken);
await Store.SetPostLogoutRedirectUrisAsync(application, ImmutableArray.CreateRange(
descriptor.PostLogoutRedirectUris.Select(address => address.OriginalString)), cancellationToken);
await Store.SetRedirectUrisAsync(application, ImmutableArray.CreateRange(
descriptor.RedirectUris.Select(address => address.OriginalString)), cancellationToken);
- await Store.SetRequirementsAsync(application, ImmutableArray.CreateRange(descriptor.Requirements), cancellationToken);
+ await Store.SetRequirementsAsync(application, descriptor.Requirements.ToImmutableArray(), cancellationToken);
}
///
@@ -860,6 +949,12 @@ namespace OpenIddict.Core
descriptor.Requirements.Clear();
descriptor.Requirements.UnionWith(await Store.GetRequirementsAsync(application, cancellationToken));
+ descriptor.DisplayNames.Clear();
+ foreach (var pair in await Store.GetDisplayNamesAsync(application, cancellationToken))
+ {
+ descriptor.DisplayNames.Add(pair.Key, pair.Value);
+ }
+
descriptor.PostLogoutRedirectUris.Clear();
foreach (var address in await Store.GetPostLogoutRedirectUrisAsync(application, cancellationToken))
{
@@ -1456,9 +1551,18 @@ namespace OpenIddict.Core
ValueTask IOpenIddictApplicationManager.GetDisplayNameAsync(object application, CancellationToken cancellationToken)
=> GetDisplayNameAsync((TApplication) application, cancellationToken);
+ ValueTask> IOpenIddictApplicationManager.GetDisplayNamesAsync(object application, CancellationToken cancellationToken)
+ => GetDisplayNamesAsync((TApplication) application, cancellationToken);
+
ValueTask IOpenIddictApplicationManager.GetIdAsync(object application, CancellationToken cancellationToken)
=> GetIdAsync((TApplication) application, cancellationToken);
+ ValueTask IOpenIddictApplicationManager.GetLocalizedDisplayNameAsync(object application, CancellationToken cancellationToken)
+ => GetLocalizedDisplayNameAsync((TApplication) application, cancellationToken);
+
+ ValueTask IOpenIddictApplicationManager.GetLocalizedDisplayNameAsync(object application, CultureInfo culture, CancellationToken cancellationToken)
+ => GetLocalizedDisplayNameAsync((TApplication) application, culture, cancellationToken);
+
ValueTask> IOpenIddictApplicationManager.GetPermissionsAsync(object application, CancellationToken cancellationToken)
=> GetPermissionsAsync((TApplication) application, cancellationToken);
diff --git a/src/OpenIddict.Core/Managers/OpenIddictScopeManager.cs b/src/OpenIddict.Core/Managers/OpenIddictScopeManager.cs
index baa43048..8ce33f07 100644
--- a/src/OpenIddict.Core/Managers/OpenIddictScopeManager.cs
+++ b/src/OpenIddict.Core/Managers/OpenIddictScopeManager.cs
@@ -8,6 +8,7 @@ using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.ComponentModel.DataAnnotations;
+using System.Globalization;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text;
@@ -420,6 +421,32 @@ namespace OpenIddict.Core
return Store.GetDescriptionAsync(scope, cancellationToken);
}
+ ///
+ /// Retrieves the localized descriptions associated with an scope.
+ ///
+ /// The scope.
+ /// The that can be used to abort the operation.
+ ///
+ /// A that can be used to monitor the asynchronous operation,
+ /// whose result returns all the localized descriptions associated with the scope.
+ ///
+ public virtual async ValueTask> GetDescriptionsAsync(
+ [NotNull] TScope scope, CancellationToken cancellationToken = default)
+ {
+ if (scope == null)
+ {
+ throw new ArgumentNullException(nameof(scope));
+ }
+
+ var descriptions = await Store.GetDescriptionsAsync(scope, cancellationToken);
+ if (descriptions == null || descriptions.Count == 0)
+ {
+ return ImmutableDictionary.Create();
+ }
+
+ return descriptions;
+ }
+
///
/// Retrieves the display name associated with a scope.
///
@@ -439,6 +466,32 @@ namespace OpenIddict.Core
return Store.GetDisplayNameAsync(scope, cancellationToken);
}
+ ///
+ /// Retrieves the localized display names associated with an scope.
+ ///
+ /// The scope.
+ /// The that can be used to abort the operation.
+ ///
+ /// A that can be used to monitor the asynchronous operation,
+ /// whose result returns all the localized display names associated with the scope.
+ ///
+ public virtual async ValueTask> GetDisplayNamesAsync(
+ [NotNull] TScope scope, CancellationToken cancellationToken = default)
+ {
+ if (scope == null)
+ {
+ throw new ArgumentNullException(nameof(scope));
+ }
+
+ var names = await Store.GetDisplayNamesAsync(scope, cancellationToken);
+ if (names == null || names.Count == 0)
+ {
+ return ImmutableDictionary.Create();
+ }
+
+ return names;
+ }
+
///
/// Retrieves the unique identifier associated with a scope.
///
@@ -458,6 +511,126 @@ namespace OpenIddict.Core
return Store.GetIdAsync(scope, cancellationToken);
}
+ ///
+ /// Retrieves the localized display name associated with an scope
+ /// and corresponding to the current UI culture or one of its parents.
+ /// If no matching value can be found, the non-localized value is returned.
+ ///
+ /// The scope.
+ /// The that can be used to abort the operation.
+ ///
+ /// A that can be used to monitor the asynchronous operation,
+ /// whose result returns the matching display name associated with the scope.
+ ///
+ public virtual ValueTask GetLocalizedDisplayNameAsync([NotNull] TScope scope, CancellationToken cancellationToken = default)
+ => GetLocalizedDisplayNameAsync(scope, CultureInfo.CurrentUICulture, cancellationToken);
+
+ ///
+ /// Retrieves the localized display name associated with an scope
+ /// and corresponding to the specified culture or one of its parents.
+ /// If no matching value can be found, the non-localized value is returned.
+ ///
+ /// The scope.
+ /// The culture (typically ).
+ /// The that can be used to abort the operation.
+ ///
+ /// A that can be used to monitor the asynchronous operation,
+ /// whose result returns the matching display name associated with the scope.
+ ///
+ public virtual async ValueTask GetLocalizedDisplayNameAsync(
+ [NotNull] TScope scope, [NotNull] CultureInfo culture, CancellationToken cancellationToken = default)
+ {
+ if (scope == null)
+ {
+ throw new ArgumentNullException(nameof(scope));
+ }
+
+ if (culture == null)
+ {
+ throw new ArgumentNullException(nameof(culture));
+ }
+
+ var names = await Store.GetDisplayNamesAsync(scope, cancellationToken);
+ if (names == null || names.IsEmpty)
+ {
+ return await Store.GetDisplayNameAsync(scope, cancellationToken);
+ }
+
+ do
+ {
+ if (names.TryGetValue(culture, out var name))
+ {
+ return name;
+ }
+
+ culture = culture.Parent;
+ }
+
+ while (culture != CultureInfo.InvariantCulture);
+
+ return await Store.GetDisplayNameAsync(scope, cancellationToken);
+ }
+
+ ///
+ /// Retrieves the localized description associated with an scope
+ /// and corresponding to the current UI culture or one of its parents.
+ /// If no matching value can be found, the non-localized value is returned.
+ ///
+ /// The scope.
+ /// The that can be used to abort the operation.
+ ///
+ /// A that can be used to monitor the asynchronous operation,
+ /// whose result returns the matching localized description associated with the scope.
+ ///
+ public virtual ValueTask GetLocalizedDescriptionAsync([NotNull] TScope scope, CancellationToken cancellationToken = default)
+ => GetLocalizedDescriptionAsync(scope, CultureInfo.CurrentUICulture, cancellationToken);
+
+ ///
+ /// Retrieves the localized description associated with an scope
+ /// and corresponding to the specified culture or one of its parents.
+ /// If no matching value can be found, the non-localized value is returned.
+ ///
+ /// The scope.
+ /// The culture (typically ).
+ /// The that can be used to abort the operation.
+ ///
+ /// A that can be used to monitor the asynchronous operation,
+ /// whose result returns the matching localized description associated with the scope.
+ ///
+ public virtual async ValueTask GetLocalizedDescriptionAsync(
+ [NotNull] TScope scope, [NotNull] CultureInfo culture, CancellationToken cancellationToken = default)
+ {
+ if (scope == null)
+ {
+ throw new ArgumentNullException(nameof(scope));
+ }
+
+ if (culture == null)
+ {
+ throw new ArgumentNullException(nameof(culture));
+ }
+
+ var descriptions = await Store.GetDescriptionsAsync(scope, cancellationToken);
+ if (descriptions == null || descriptions.IsEmpty)
+ {
+ return await Store.GetDescriptionAsync(scope, cancellationToken);
+ }
+
+ do
+ {
+ if (descriptions.TryGetValue(culture, out var description))
+ {
+ return description;
+ }
+
+ culture = culture.Parent;
+ }
+
+ while (culture != CultureInfo.InvariantCulture);
+
+ return await Store.GetDescriptionAsync(scope, cancellationToken);
+ }
+
///
/// Retrieves the name associated with a scope.
///
@@ -592,7 +765,9 @@ namespace OpenIddict.Core
}
await Store.SetDescriptionAsync(scope, descriptor.Description, cancellationToken);
+ await Store.SetDescriptionsAsync(scope, descriptor.Descriptions.ToImmutableDictionary(), cancellationToken);
await Store.SetDisplayNameAsync(scope, descriptor.DisplayName, cancellationToken);
+ await Store.SetDisplayNamesAsync(scope, descriptor.DisplayNames.ToImmutableDictionary(), cancellationToken);
await Store.SetNameAsync(scope, descriptor.Name, cancellationToken);
await Store.SetResourcesAsync(scope, descriptor.Resources.ToImmutableArray(), cancellationToken);
}
@@ -625,6 +800,18 @@ namespace OpenIddict.Core
descriptor.Name = await Store.GetNameAsync(scope, cancellationToken);
descriptor.Resources.Clear();
descriptor.Resources.UnionWith(await Store.GetResourcesAsync(scope, cancellationToken));
+
+ descriptor.DisplayNames.Clear();
+ foreach (var pair in await Store.GetDisplayNamesAsync(scope, cancellationToken))
+ {
+ descriptor.DisplayNames.Add(pair.Key, pair.Value);
+ }
+
+ descriptor.Descriptions.Clear();
+ foreach (var pair in await Store.GetDescriptionsAsync(scope, cancellationToken))
+ {
+ descriptor.Descriptions.Add(pair.Key, pair.Value);
+ }
}
///
@@ -784,12 +971,30 @@ namespace OpenIddict.Core
ValueTask IOpenIddictScopeManager.GetDescriptionAsync(object scope, CancellationToken cancellationToken)
=> GetDescriptionAsync((TScope) scope, cancellationToken);
+ ValueTask> IOpenIddictScopeManager.GetDescriptionsAsync(object scope, CancellationToken cancellationToken)
+ => GetDescriptionsAsync((TScope) scope, cancellationToken);
+
ValueTask IOpenIddictScopeManager.GetDisplayNameAsync(object scope, CancellationToken cancellationToken)
=> GetDisplayNameAsync((TScope) scope, cancellationToken);
+ ValueTask> IOpenIddictScopeManager.GetDisplayNamesAsync(object scope, CancellationToken cancellationToken)
+ => GetDisplayNamesAsync((TScope) scope, cancellationToken);
+
ValueTask IOpenIddictScopeManager.GetIdAsync(object scope, CancellationToken cancellationToken)
=> GetIdAsync((TScope) scope, cancellationToken);
+ ValueTask IOpenIddictScopeManager.GetLocalizedDescriptionAsync(object scope, CancellationToken cancellationToken)
+ => GetLocalizedDescriptionAsync((TScope) scope, cancellationToken);
+
+ ValueTask IOpenIddictScopeManager.GetLocalizedDescriptionAsync(object scope, CultureInfo culture, CancellationToken cancellationToken)
+ => GetLocalizedDescriptionAsync((TScope) scope, culture, cancellationToken);
+
+ ValueTask IOpenIddictScopeManager.GetLocalizedDisplayNameAsync(object scope, CancellationToken cancellationToken)
+ => GetLocalizedDisplayNameAsync((TScope) scope, cancellationToken);
+
+ ValueTask IOpenIddictScopeManager.GetLocalizedDisplayNameAsync(object scope, CultureInfo culture, CancellationToken cancellationToken)
+ => GetLocalizedDisplayNameAsync((TScope) scope, culture, cancellationToken);
+
ValueTask IOpenIddictScopeManager.GetNameAsync(object scope, CancellationToken cancellationToken)
=> GetNameAsync((TScope) scope, cancellationToken);
diff --git a/src/OpenIddict.EntityFramework.Models/OpenIddictEntityFrameworkApplication.cs b/src/OpenIddict.EntityFramework.Models/OpenIddictEntityFrameworkApplication.cs
index d8812432..f9e9b29d 100644
--- a/src/OpenIddict.EntityFramework.Models/OpenIddictEntityFrameworkApplication.cs
+++ b/src/OpenIddict.EntityFramework.Models/OpenIddictEntityFrameworkApplication.cs
@@ -63,6 +63,13 @@ namespace OpenIddict.EntityFramework.Models
///
public virtual string DisplayName { get; set; }
+ ///
+ /// Gets or sets the localized display names
+ /// associated with the current application,
+ /// serialized as a JSON object.
+ ///
+ public virtual string DisplayNames { get; set; }
+
///
/// Gets or sets the unique identifier
/// associated with the current application.
diff --git a/src/OpenIddict.EntityFramework.Models/OpenIddictEntityFrameworkScope.cs b/src/OpenIddict.EntityFramework.Models/OpenIddictEntityFrameworkScope.cs
index b9c14066..9f719c28 100644
--- a/src/OpenIddict.EntityFramework.Models/OpenIddictEntityFrameworkScope.cs
+++ b/src/OpenIddict.EntityFramework.Models/OpenIddictEntityFrameworkScope.cs
@@ -38,12 +38,25 @@ namespace OpenIddict.EntityFramework.Models
///
public virtual string Description { get; set; }
+ ///
+ /// Gets or sets the localized public descriptions associated
+ /// with the current scope, serialized as a JSON object.
+ ///
+ public virtual string Descriptions { get; set; }
+
///
/// Gets or sets the display name
/// associated with the current scope.
///
public virtual string DisplayName { get; set; }
+ ///
+ /// Gets or sets the localized display names
+ /// associated with the current application,
+ /// serialized as a JSON object.
+ ///
+ public virtual string DisplayNames { get; set; }
+
///
/// Gets or sets the unique identifier
/// associated with the current scope.
diff --git a/src/OpenIddict.EntityFramework/Stores/OpenIddictEntityFrameworkApplicationStore.cs b/src/OpenIddict.EntityFramework/Stores/OpenIddictEntityFrameworkApplicationStore.cs
index b0ade607..ef7bfa77 100644
--- a/src/OpenIddict.EntityFramework/Stores/OpenIddictEntityFrameworkApplicationStore.cs
+++ b/src/OpenIddict.EntityFramework/Stores/OpenIddictEntityFrameworkApplicationStore.cs
@@ -11,6 +11,8 @@ using System.ComponentModel;
using System.Data;
using System.Data.Entity;
using System.Data.Entity.Infrastructure;
+using System.Globalization;
+using System.IO;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text;
@@ -484,6 +486,42 @@ namespace OpenIddict.EntityFramework
return new ValueTask(application.DisplayName);
}
+ ///
+ /// Retrieves the localized display names associated with an application.
+ ///
+ /// The application.
+ /// The that can be used to abort the operation.
+ ///
+ /// A that can be used to monitor the asynchronous operation,
+ /// whose result returns all the localized display names associated with the application.
+ ///
+ public virtual ValueTask> GetDisplayNamesAsync([NotNull] TApplication application, CancellationToken cancellationToken)
+ {
+ if (application == null)
+ {
+ throw new ArgumentNullException(nameof(application));
+ }
+
+ if (string.IsNullOrEmpty(application.DisplayNames))
+ {
+ return new ValueTask>(ImmutableDictionary.Create());
+ }
+
+ // Note: parsing the stringified display names is an expensive operation.
+ // To mitigate that, the resulting object is stored in the memory cache.
+ var key = string.Concat("7762c378-c113-4564-b14b-1402b3949aaa", "\x1e", application.DisplayNames);
+ var names = Cache.GetOrCreate(key, entry =>
+ {
+ entry.SetPriority(CacheItemPriority.High)
+ .SetSlidingExpiration(TimeSpan.FromMinutes(1));
+
+ return JsonSerializer.Deserialize>(application.DisplayNames)
+ .ToImmutableDictionary(name => CultureInfo.GetCultureInfo(name.Key), name => name.Value);
+ });
+
+ return new ValueTask>(names);
+ }
+
///
/// Retrieves the unique identifier associated with an application.
///
@@ -852,6 +890,51 @@ namespace OpenIddict.EntityFramework
return default;
}
+ ///
+ /// Sets the localized display names associated with an application.
+ ///
+ /// The application.
+ /// The localized display names associated with the application.
+ /// The that can be used to abort the operation.
+ /// A that can be used to monitor the asynchronous operation.
+ public virtual ValueTask SetDisplayNamesAsync([NotNull] TApplication application,
+ [CanBeNull] ImmutableDictionary names, CancellationToken cancellationToken)
+ {
+ if (application == null)
+ {
+ throw new ArgumentNullException(nameof(application));
+ }
+
+ if (names == null || names.IsEmpty)
+ {
+ application.DisplayNames = null;
+
+ return default;
+ }
+
+ using var stream = new MemoryStream();
+ using var writer = new Utf8JsonWriter(stream, new JsonWriterOptions
+ {
+ Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping,
+ Indented = false
+ });
+
+ writer.WriteStartObject();
+
+ foreach (var name in names)
+ {
+ writer.WritePropertyName(name.Key.Name);
+ writer.WriteStringValue(name.Value);
+ }
+
+ writer.WriteEndObject();
+ writer.Flush();
+
+ application.DisplayNames = Encoding.UTF8.GetString(stream.ToArray());
+
+ return default;
+ }
+
///
/// Sets the permissions associated with an application.
///
diff --git a/src/OpenIddict.EntityFramework/Stores/OpenIddictEntityFrameworkScopeStore.cs b/src/OpenIddict.EntityFramework/Stores/OpenIddictEntityFrameworkScopeStore.cs
index b1ac4fa4..c43a3c24 100644
--- a/src/OpenIddict.EntityFramework/Stores/OpenIddictEntityFrameworkScopeStore.cs
+++ b/src/OpenIddict.EntityFramework/Stores/OpenIddictEntityFrameworkScopeStore.cs
@@ -10,6 +10,8 @@ using System.Collections.Immutable;
using System.ComponentModel;
using System.Data.Entity;
using System.Data.Entity.Infrastructure;
+using System.Globalization;
+using System.IO;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text;
@@ -311,6 +313,42 @@ namespace OpenIddict.EntityFramework
return new ValueTask(scope.Description);
}
+ ///
+ /// Retrieves the localized descriptions associated with a scope.
+ ///
+ /// The scope.
+ /// The that can be used to abort the operation.
+ ///
+ /// A that can be used to monitor the asynchronous operation,
+ /// whose result returns all the localized descriptions associated with the specified scope.
+ ///
+ public virtual ValueTask> GetDescriptionsAsync([NotNull] TScope scope, CancellationToken cancellationToken)
+ {
+ if (scope == null)
+ {
+ throw new ArgumentNullException(nameof(scope));
+ }
+
+ if (string.IsNullOrEmpty(scope.Descriptions))
+ {
+ return new ValueTask>(ImmutableDictionary.Create());
+ }
+
+ // Note: parsing the stringified descriptions is an expensive operation.
+ // To mitigate that, the resulting object is stored in the memory cache.
+ var key = string.Concat("42891062-8f69-43ba-9111-db7e8ded2553", "\x1e", scope.Descriptions);
+ var descriptions = Cache.GetOrCreate(key, entry =>
+ {
+ entry.SetPriority(CacheItemPriority.High)
+ .SetSlidingExpiration(TimeSpan.FromMinutes(1));
+
+ return JsonSerializer.Deserialize>(scope.Descriptions)
+ .ToImmutableDictionary(description => CultureInfo.GetCultureInfo(description.Key), description => description.Value);
+ });
+
+ return new ValueTask>(descriptions);
+ }
+
///
/// Retrieves the display name associated with a scope.
///
@@ -330,6 +368,42 @@ namespace OpenIddict.EntityFramework
return new ValueTask(scope.DisplayName);
}
+ ///
+ /// Retrieves the localized display names associated with a scope.
+ ///
+ /// The scope.
+ /// The that can be used to abort the operation.
+ ///
+ /// A that can be used to monitor the asynchronous operation,
+ /// whose result returns all the localized display names associated with the scope.
+ ///
+ public virtual ValueTask> GetDisplayNamesAsync([NotNull] TScope scope, CancellationToken cancellationToken)
+ {
+ if (scope == null)
+ {
+ throw new ArgumentNullException(nameof(scope));
+ }
+
+ if (string.IsNullOrEmpty(scope.DisplayNames))
+ {
+ return new ValueTask>(ImmutableDictionary.Create());
+ }
+
+ // Note: parsing the stringified display names is an expensive operation.
+ // To mitigate that, the resulting object is stored in the memory cache.
+ var key = string.Concat("e17d437b-bdd2-43f3-974e-46d524f4bae1", "\x1e", scope.DisplayNames);
+ var names = Cache.GetOrCreate(key, entry =>
+ {
+ entry.SetPriority(CacheItemPriority.High)
+ .SetSlidingExpiration(TimeSpan.FromMinutes(1));
+
+ return JsonSerializer.Deserialize>(scope.DisplayNames)
+ .ToImmutableDictionary(name => CultureInfo.GetCultureInfo(name.Key), name => name.Value);
+ });
+
+ return new ValueTask>(names);
+ }
+
///
/// Retrieves the unique identifier associated with a scope.
///
@@ -529,6 +603,51 @@ namespace OpenIddict.EntityFramework
return default;
}
+ ///
+ /// Sets the localized descriptions associated with a scope.
+ ///
+ /// The scope.
+ /// The localized descriptions associated with the authorization.
+ /// The that can be used to abort the operation.
+ /// A that can be used to monitor the asynchronous operation.
+ public virtual ValueTask SetDescriptionsAsync([NotNull] TScope scope,
+ [CanBeNull] ImmutableDictionary descriptions, CancellationToken cancellationToken)
+ {
+ if (scope == null)
+ {
+ throw new ArgumentNullException(nameof(scope));
+ }
+
+ if (descriptions == null || descriptions.IsEmpty)
+ {
+ scope.Descriptions = null;
+
+ return default;
+ }
+
+ using var stream = new MemoryStream();
+ using var writer = new Utf8JsonWriter(stream, new JsonWriterOptions
+ {
+ Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping,
+ Indented = false
+ });
+
+ writer.WriteStartObject();
+
+ foreach (var description in descriptions)
+ {
+ writer.WritePropertyName(description.Key.Name);
+ writer.WriteStringValue(description.Value);
+ }
+
+ writer.WriteEndObject();
+ writer.Flush();
+
+ scope.Descriptions = Encoding.UTF8.GetString(stream.ToArray());
+
+ return default;
+ }
+
///
/// Sets the display name associated with a scope.
///
@@ -548,6 +667,51 @@ namespace OpenIddict.EntityFramework
return default;
}
+ ///
+ /// Sets the localized display names associated with a scope.
+ ///
+ /// The scope.
+ /// The localized display names associated with the scope.
+ /// The that can be used to abort the operation.
+ /// A that can be used to monitor the asynchronous operation.
+ public virtual ValueTask SetDisplayNamesAsync([NotNull] TScope scope,
+ [CanBeNull] ImmutableDictionary names, CancellationToken cancellationToken)
+ {
+ if (scope == null)
+ {
+ throw new ArgumentNullException(nameof(scope));
+ }
+
+ if (names == null || names.IsEmpty)
+ {
+ scope.DisplayNames = null;
+
+ return default;
+ }
+
+ using var stream = new MemoryStream();
+ using var writer = new Utf8JsonWriter(stream, new JsonWriterOptions
+ {
+ Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping,
+ Indented = false
+ });
+
+ writer.WriteStartObject();
+
+ foreach (var name in names)
+ {
+ writer.WritePropertyName(name.Key.Name);
+ writer.WriteStringValue(name.Value);
+ }
+
+ writer.WriteEndObject();
+ writer.Flush();
+
+ scope.DisplayNames = Encoding.UTF8.GetString(stream.ToArray());
+
+ return default;
+ }
+
///
/// Sets the name associated with a scope.
///
diff --git a/src/OpenIddict.EntityFrameworkCore.Models/OpenIddictEntityFrameworkCoreApplication.cs b/src/OpenIddict.EntityFrameworkCore.Models/OpenIddictEntityFrameworkCoreApplication.cs
index 45e86c23..3188a9ec 100644
--- a/src/OpenIddict.EntityFrameworkCore.Models/OpenIddictEntityFrameworkCoreApplication.cs
+++ b/src/OpenIddict.EntityFrameworkCore.Models/OpenIddictEntityFrameworkCoreApplication.cs
@@ -71,6 +71,13 @@ namespace OpenIddict.EntityFrameworkCore.Models
///
public virtual string DisplayName { get; set; }
+ ///
+ /// Gets or sets the localized display names
+ /// associated with the current application,
+ /// serialized as a JSON object.
+ ///
+ public virtual string DisplayNames { get; set; }
+
///
/// Gets or sets the unique identifier
/// associated with the current application.
diff --git a/src/OpenIddict.EntityFrameworkCore.Models/OpenIddictEntityFrameworkCoreScope.cs b/src/OpenIddict.EntityFrameworkCore.Models/OpenIddictEntityFrameworkCoreScope.cs
index 2acac51e..a1e0e9ce 100644
--- a/src/OpenIddict.EntityFrameworkCore.Models/OpenIddictEntityFrameworkCoreScope.cs
+++ b/src/OpenIddict.EntityFrameworkCore.Models/OpenIddictEntityFrameworkCoreScope.cs
@@ -38,12 +38,25 @@ namespace OpenIddict.EntityFrameworkCore.Models
///
public virtual string Description { get; set; }
+ ///
+ /// Gets or sets the localized public descriptions associated
+ /// with the current scope, serialized as a JSON object.
+ ///
+ public virtual string Descriptions { get; set; }
+
///
/// Gets or sets the display name
/// associated with the current scope.
///
public virtual string DisplayName { get; set; }
+ ///
+ /// Gets or sets the localized display names
+ /// associated with the current application,
+ /// serialized as a JSON object.
+ ///
+ public virtual string DisplayNames { get; set; }
+
///
/// Gets or sets the unique identifier
/// associated with the current scope.
diff --git a/src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictEntityFrameworkCoreApplicationStore.cs b/src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictEntityFrameworkCoreApplicationStore.cs
index 72068d10..8c885386 100644
--- a/src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictEntityFrameworkCoreApplicationStore.cs
+++ b/src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictEntityFrameworkCoreApplicationStore.cs
@@ -9,6 +9,8 @@ using System.Collections.Generic;
using System.Collections.Immutable;
using System.ComponentModel;
using System.Data;
+using System.Globalization;
+using System.IO;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text;
@@ -528,6 +530,42 @@ namespace OpenIddict.EntityFrameworkCore
return new ValueTask(application.DisplayName);
}
+ ///
+ /// Retrieves the localized display names associated with an application.
+ ///
+ /// The application.
+ /// The that can be used to abort the operation.
+ ///
+ /// A that can be used to monitor the asynchronous operation,
+ /// whose result returns all the localized display names associated with the application.
+ ///
+ public virtual ValueTask> GetDisplayNamesAsync([NotNull] TApplication application, CancellationToken cancellationToken)
+ {
+ if (application == null)
+ {
+ throw new ArgumentNullException(nameof(application));
+ }
+
+ if (string.IsNullOrEmpty(application.DisplayNames))
+ {
+ return new ValueTask>(ImmutableDictionary.Create());
+ }
+
+ // Note: parsing the stringified display names is an expensive operation.
+ // To mitigate that, the resulting object is stored in the memory cache.
+ var key = string.Concat("7762c378-c113-4564-b14b-1402b3949aaa", "\x1e", application.DisplayNames);
+ var names = Cache.GetOrCreate(key, entry =>
+ {
+ entry.SetPriority(CacheItemPriority.High)
+ .SetSlidingExpiration(TimeSpan.FromMinutes(1));
+
+ return JsonSerializer.Deserialize>(application.DisplayNames)
+ .ToImmutableDictionary(name => CultureInfo.GetCultureInfo(name.Key), name => name.Value);
+ });
+
+ return new ValueTask>(names);
+ }
+
///
/// Retrieves the unique identifier associated with an application.
///
@@ -896,6 +934,51 @@ namespace OpenIddict.EntityFrameworkCore
return default;
}
+ ///
+ /// Sets the localized display names associated with an application.
+ ///
+ /// The application.
+ /// The localized display names associated with the application.
+ /// The that can be used to abort the operation.
+ /// A that can be used to monitor the asynchronous operation.
+ public virtual ValueTask SetDisplayNamesAsync([NotNull] TApplication application,
+ [CanBeNull] ImmutableDictionary names, CancellationToken cancellationToken)
+ {
+ if (application == null)
+ {
+ throw new ArgumentNullException(nameof(application));
+ }
+
+ if (names == null || names.IsEmpty)
+ {
+ application.DisplayNames = null;
+
+ return default;
+ }
+
+ using var stream = new MemoryStream();
+ using var writer = new Utf8JsonWriter(stream, new JsonWriterOptions
+ {
+ Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping,
+ Indented = false
+ });
+
+ writer.WriteStartObject();
+
+ foreach (var pair in names)
+ {
+ writer.WritePropertyName(pair.Key.Name);
+ writer.WriteStringValue(pair.Value);
+ }
+
+ writer.WriteEndObject();
+ writer.Flush();
+
+ application.DisplayNames = Encoding.UTF8.GetString(stream.ToArray());
+
+ return default;
+ }
+
///
/// Sets the permissions associated with an application.
///
diff --git a/src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictEntityFrameworkCoreScopeStore.cs b/src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictEntityFrameworkCoreScopeStore.cs
index b3527d67..305f60f6 100644
--- a/src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictEntityFrameworkCoreScopeStore.cs
+++ b/src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictEntityFrameworkCoreScopeStore.cs
@@ -8,6 +8,8 @@ using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.ComponentModel;
+using System.Globalization;
+using System.IO;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text;
@@ -327,6 +329,42 @@ namespace OpenIddict.EntityFrameworkCore
return new ValueTask(scope.Description);
}
+ ///
+ /// Retrieves the localized descriptions associated with a scope.
+ ///
+ /// The scope.
+ /// The that can be used to abort the operation.
+ ///
+ /// A that can be used to monitor the asynchronous operation,
+ /// whose result returns all the localized descriptions associated with the specified scope.
+ ///
+ public virtual ValueTask> GetDescriptionsAsync([NotNull] TScope scope, CancellationToken cancellationToken)
+ {
+ if (scope == null)
+ {
+ throw new ArgumentNullException(nameof(scope));
+ }
+
+ if (string.IsNullOrEmpty(scope.Descriptions))
+ {
+ return new ValueTask>(ImmutableDictionary.Create());
+ }
+
+ // Note: parsing the stringified descriptions is an expensive operation.
+ // To mitigate that, the resulting object is stored in the memory cache.
+ var key = string.Concat("42891062-8f69-43ba-9111-db7e8ded2553", "\x1e", scope.Descriptions);
+ var descriptions = Cache.GetOrCreate(key, entry =>
+ {
+ entry.SetPriority(CacheItemPriority.High)
+ .SetSlidingExpiration(TimeSpan.FromMinutes(1));
+
+ return JsonSerializer.Deserialize>(scope.Descriptions)
+ .ToImmutableDictionary(description => CultureInfo.GetCultureInfo(description.Key), description => description.Value);
+ });
+
+ return new ValueTask>(descriptions);
+ }
+
///
/// Retrieves the display name associated with a scope.
///
@@ -346,6 +384,42 @@ namespace OpenIddict.EntityFrameworkCore
return new ValueTask(scope.DisplayName);
}
+ ///
+ /// Retrieves the localized display names associated with a scope.
+ ///
+ /// The scope.
+ /// The that can be used to abort the operation.
+ ///
+ /// A that can be used to monitor the asynchronous operation,
+ /// whose result returns all the localized display names associated with the scope.
+ ///
+ public virtual ValueTask> GetDisplayNamesAsync([NotNull] TScope scope, CancellationToken cancellationToken)
+ {
+ if (scope == null)
+ {
+ throw new ArgumentNullException(nameof(scope));
+ }
+
+ if (string.IsNullOrEmpty(scope.DisplayNames))
+ {
+ return new ValueTask>(ImmutableDictionary.Create());
+ }
+
+ // Note: parsing the stringified display names is an expensive operation.
+ // To mitigate that, the resulting object is stored in the memory cache.
+ var key = string.Concat("e17d437b-bdd2-43f3-974e-46d524f4bae1", "\x1e", scope.DisplayNames);
+ var names = Cache.GetOrCreate(key, entry =>
+ {
+ entry.SetPriority(CacheItemPriority.High)
+ .SetSlidingExpiration(TimeSpan.FromMinutes(1));
+
+ return JsonSerializer.Deserialize>(scope.DisplayNames)
+ .ToImmutableDictionary(name => CultureInfo.GetCultureInfo(name.Key), name => name.Value);
+ });
+
+ return new ValueTask>(names);
+ }
+
///
/// Retrieves the unique identifier associated with a scope.
///
@@ -545,6 +619,51 @@ namespace OpenIddict.EntityFrameworkCore
return default;
}
+ ///
+ /// Sets the localized descriptions associated with a scope.
+ ///
+ /// The scope.
+ /// The localized descriptions associated with the authorization.
+ /// The that can be used to abort the operation.
+ /// A that can be used to monitor the asynchronous operation.
+ public virtual ValueTask SetDescriptionsAsync([NotNull] TScope scope,
+ [CanBeNull] ImmutableDictionary descriptions, CancellationToken cancellationToken)
+ {
+ if (scope == null)
+ {
+ throw new ArgumentNullException(nameof(scope));
+ }
+
+ if (descriptions == null || descriptions.IsEmpty)
+ {
+ scope.Descriptions = null;
+
+ return default;
+ }
+
+ using var stream = new MemoryStream();
+ using var writer = new Utf8JsonWriter(stream, new JsonWriterOptions
+ {
+ Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping,
+ Indented = false
+ });
+
+ writer.WriteStartObject();
+
+ foreach (var description in descriptions)
+ {
+ writer.WritePropertyName(description.Key.Name);
+ writer.WriteStringValue(description.Value);
+ }
+
+ writer.WriteEndObject();
+ writer.Flush();
+
+ scope.Descriptions = Encoding.UTF8.GetString(stream.ToArray());
+
+ return default;
+ }
+
///
/// Sets the display name associated with a scope.
///
@@ -564,6 +683,51 @@ namespace OpenIddict.EntityFrameworkCore
return default;
}
+ ///
+ /// Sets the localized display names associated with a scope.
+ ///
+ /// The scope.
+ /// The localized display names associated with the scope.
+ /// The that can be used to abort the operation.
+ /// A that can be used to monitor the asynchronous operation.
+ public virtual ValueTask SetDisplayNamesAsync([NotNull] TScope scope,
+ [CanBeNull] ImmutableDictionary names, CancellationToken cancellationToken)
+ {
+ if (scope == null)
+ {
+ throw new ArgumentNullException(nameof(scope));
+ }
+
+ if (names == null || names.IsEmpty)
+ {
+ scope.DisplayNames = null;
+
+ return default;
+ }
+
+ using var stream = new MemoryStream();
+ using var writer = new Utf8JsonWriter(stream, new JsonWriterOptions
+ {
+ Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping,
+ Indented = false
+ });
+
+ writer.WriteStartObject();
+
+ foreach (var name in names)
+ {
+ writer.WritePropertyName(name.Key.Name);
+ writer.WriteStringValue(name.Value);
+ }
+
+ writer.WriteEndObject();
+ writer.Flush();
+
+ scope.DisplayNames = Encoding.UTF8.GetString(stream.ToArray());
+
+ return default;
+ }
+
///
/// Sets the name associated with a scope.
///
diff --git a/src/OpenIddict.MongoDb.Models/OpenIddict.MongoDb.Models.csproj b/src/OpenIddict.MongoDb.Models/OpenIddict.MongoDb.Models.csproj
index 0b6fd067..bfde10fb 100644
--- a/src/OpenIddict.MongoDb.Models/OpenIddict.MongoDb.Models.csproj
+++ b/src/OpenIddict.MongoDb.Models/OpenIddict.MongoDb.Models.csproj
@@ -13,6 +13,7 @@
+
diff --git a/src/OpenIddict.MongoDb.Models/OpenIddictMongoDbApplication.cs b/src/OpenIddict.MongoDb.Models/OpenIddictMongoDbApplication.cs
index e885c4a5..148a5252 100644
--- a/src/OpenIddict.MongoDb.Models/OpenIddictMongoDbApplication.cs
+++ b/src/OpenIddict.MongoDb.Models/OpenIddictMongoDbApplication.cs
@@ -5,7 +5,10 @@
*/
using System;
+using System.Collections.Generic;
+using System.Collections.Immutable;
using System.Diagnostics;
+using System.Globalization;
using MongoDB.Bson;
using MongoDB.Bson.Serialization.Attributes;
@@ -52,6 +55,14 @@ namespace OpenIddict.MongoDb.Models
[BsonElement("display_name"), BsonIgnoreIfNull]
public virtual string DisplayName { get; set; }
+ ///
+ /// Gets or sets the localized display names
+ /// associated with the current application.
+ ///
+ [BsonElement("display_names"), BsonIgnoreIfNull]
+ public virtual IReadOnlyDictionary DisplayNames { get; set; }
+ = ImmutableDictionary.Create();
+
///
/// Gets or sets the unique identifier
/// associated with the current application.
@@ -63,13 +74,13 @@ namespace OpenIddict.MongoDb.Models
/// Gets or sets the permissions associated with the current application.
///
[BsonElement("permissions"), BsonIgnoreIfDefault]
- public virtual string[] Permissions { get; set; } = Array.Empty();
+ public virtual IReadOnlyList Permissions { get; set; } = ImmutableArray.Create();
///
/// Gets or sets the logout callback URLs associated with the current application.
///
[BsonElement("post_logout_redirect_uris"), BsonIgnoreIfDefault]
- public virtual string[] PostLogoutRedirectUris { get; set; } = Array.Empty();
+ public virtual IReadOnlyList PostLogoutRedirectUris { get; set; } = ImmutableArray.Create();
///
/// Gets or sets the additional properties associated with the current application.
@@ -81,13 +92,13 @@ namespace OpenIddict.MongoDb.Models
/// Gets or sets the callback URLs associated with the current application.
///
[BsonElement("redirect_uris"), BsonIgnoreIfDefault]
- public virtual string[] RedirectUris { get; set; } = Array.Empty();
+ public virtual IReadOnlyList RedirectUris { get; set; } = ImmutableArray.Create();
///
/// Gets or sets the requirements associated with the current application.
///
[BsonElement("requirements"), BsonIgnoreIfDefault]
- public virtual string[] Requirements { get; set; } = Array.Empty();
+ public virtual IReadOnlyList Requirements { get; set; } = ImmutableArray.Create();
///
/// Gets or sets the application type
diff --git a/src/OpenIddict.MongoDb.Models/OpenIddictMongoDbAuthorization.cs b/src/OpenIddict.MongoDb.Models/OpenIddictMongoDbAuthorization.cs
index 19ef8faf..f3b646b4 100644
--- a/src/OpenIddict.MongoDb.Models/OpenIddictMongoDbAuthorization.cs
+++ b/src/OpenIddict.MongoDb.Models/OpenIddictMongoDbAuthorization.cs
@@ -5,6 +5,8 @@
*/
using System;
+using System.Collections.Generic;
+using System.Collections.Immutable;
using System.Diagnostics;
using MongoDB.Bson;
using MongoDB.Bson.Serialization.Attributes;
@@ -47,7 +49,7 @@ namespace OpenIddict.MongoDb.Models
/// Gets or sets the scopes associated with the current authorization.
///
[BsonElement("scopes"), BsonIgnoreIfDefault]
- public virtual string[] Scopes { get; set; } = Array.Empty();
+ public virtual IReadOnlyList Scopes { get; set; } = ImmutableArray.Create();
///
/// Gets or sets the status of the current authorization.
diff --git a/src/OpenIddict.MongoDb.Models/OpenIddictMongoDbScope.cs b/src/OpenIddict.MongoDb.Models/OpenIddictMongoDbScope.cs
index 413e50fd..f23ee263 100644
--- a/src/OpenIddict.MongoDb.Models/OpenIddictMongoDbScope.cs
+++ b/src/OpenIddict.MongoDb.Models/OpenIddictMongoDbScope.cs
@@ -5,7 +5,10 @@
*/
using System;
+using System.Collections.Generic;
+using System.Collections.Immutable;
using System.Diagnostics;
+using System.Globalization;
using MongoDB.Bson;
using MongoDB.Bson.Serialization.Attributes;
@@ -30,6 +33,14 @@ namespace OpenIddict.MongoDb.Models
[BsonElement("description"), BsonIgnoreIfNull]
public virtual string Description { get; set; }
+ ///
+ /// Gets or sets the localized public descriptions
+ /// associated with the current scope.
+ ///
+ [BsonElement("descriptions"), BsonIgnoreIfNull]
+ public virtual IReadOnlyDictionary Descriptions { get; set; }
+ = ImmutableDictionary.Create();
+
///
/// Gets or sets the display name
/// associated with the current scope.
@@ -37,6 +48,14 @@ namespace OpenIddict.MongoDb.Models
[BsonElement("display_name"), BsonIgnoreIfNull]
public virtual string DisplayName { get; set; }
+ ///
+ /// Gets or sets the localized display names
+ /// associated with the current scope.
+ ///
+ [BsonElement("display_names"), BsonIgnoreIfNull]
+ public virtual IReadOnlyDictionary DisplayNames { get; set; }
+ = ImmutableDictionary.Create();
+
///
/// Gets or sets the unique identifier
/// associated with the current scope.
@@ -61,6 +80,6 @@ namespace OpenIddict.MongoDb.Models
/// Gets or sets the resources associated with the current scope.
///
[BsonElement("resources"), BsonIgnoreIfDefault]
- public virtual string[] Resources { get; set; } = Array.Empty();
+ public virtual IReadOnlyList Resources { get; set; } = ImmutableArray.Create();
}
}
diff --git a/src/OpenIddict.MongoDb/Stores/OpenIddictMongoDbApplicationStore.cs b/src/OpenIddict.MongoDb/Stores/OpenIddictMongoDbApplicationStore.cs
index 1e7f484c..7a0a4272 100644
--- a/src/OpenIddict.MongoDb/Stores/OpenIddictMongoDbApplicationStore.cs
+++ b/src/OpenIddict.MongoDb/Stores/OpenIddictMongoDbApplicationStore.cs
@@ -7,6 +7,7 @@
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
+using System.Globalization;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text;
@@ -370,6 +371,30 @@ namespace OpenIddict.MongoDb
return new ValueTask(application.DisplayName);
}
+ ///
+ /// Retrieves the localized display names associated with an application.
+ ///
+ /// The application.
+ /// The that can be used to abort the operation.
+ ///
+ /// A that can be used to monitor the asynchronous operation,
+ /// whose result returns all the localized display names associated with the application.
+ ///
+ public virtual ValueTask> GetDisplayNamesAsync([NotNull] TApplication application, CancellationToken cancellationToken)
+ {
+ if (application == null)
+ {
+ throw new ArgumentNullException(nameof(application));
+ }
+
+ if (application.DisplayNames == null || application.DisplayNames.Count == 0)
+ {
+ return new ValueTask>(ImmutableDictionary.Create());
+ }
+
+ return new ValueTask>(application.DisplayNames.ToImmutableDictionary());
+ }
+
///
/// Retrieves the unique identifier associated with an application.
///
@@ -406,7 +431,7 @@ namespace OpenIddict.MongoDb
throw new ArgumentNullException(nameof(application));
}
- if (application.Permissions == null || application.Permissions.Length == 0)
+ if (application.Permissions == null || application.Permissions.Count == 0)
{
return new ValueTask>(ImmutableArray.Create());
}
@@ -431,7 +456,7 @@ namespace OpenIddict.MongoDb
throw new ArgumentNullException(nameof(application));
}
- if (application.PostLogoutRedirectUris == null || application.PostLogoutRedirectUris.Length == 0)
+ if (application.PostLogoutRedirectUris == null || application.PostLogoutRedirectUris.Count == 0)
{
return new ValueTask>(ImmutableArray.Create());
}
@@ -481,7 +506,7 @@ namespace OpenIddict.MongoDb
throw new ArgumentNullException(nameof(application));
}
- if (application.RedirectUris == null || application.RedirectUris.Length == 0)
+ if (application.RedirectUris == null || application.RedirectUris.Count == 0)
{
return new ValueTask>(ImmutableArray.Create());
}
@@ -505,7 +530,7 @@ namespace OpenIddict.MongoDb
throw new ArgumentNullException(nameof(application));
}
- if (application.Requirements == null || application.Requirements.Length == 0)
+ if (application.Requirements == null || application.Requirements.Count == 0)
{
return new ValueTask>(ImmutableArray.Create());
}
@@ -704,6 +729,26 @@ namespace OpenIddict.MongoDb
return default;
}
+ ///
+ /// Sets the localized display names associated with an application.
+ ///
+ /// The application.
+ /// The localized display names associated with the application.
+ /// The that can be used to abort the operation.
+ /// A that can be used to monitor the asynchronous operation.
+ public virtual ValueTask SetDisplayNamesAsync([NotNull] TApplication application,
+ [CanBeNull] ImmutableDictionary names, CancellationToken cancellationToken)
+ {
+ if (application == null)
+ {
+ throw new ArgumentNullException(nameof(application));
+ }
+
+ application.DisplayNames = names;
+
+ return default;
+ }
+
///
/// Sets the permissions associated with an application.
///
diff --git a/src/OpenIddict.MongoDb/Stores/OpenIddictMongoDbAuthorizationStore.cs b/src/OpenIddict.MongoDb/Stores/OpenIddictMongoDbAuthorizationStore.cs
index c33a0da0..0c5e1728 100644
--- a/src/OpenIddict.MongoDb/Stores/OpenIddictMongoDbAuthorizationStore.cs
+++ b/src/OpenIddict.MongoDb/Stores/OpenIddictMongoDbAuthorizationStore.cs
@@ -515,7 +515,7 @@ namespace OpenIddict.MongoDb
throw new ArgumentNullException(nameof(authorization));
}
- if (authorization.Scopes == null || authorization.Scopes.Length == 0)
+ if (authorization.Scopes == null || authorization.Scopes.Count == 0)
{
return new ValueTask>(ImmutableArray.Create());
}
diff --git a/src/OpenIddict.MongoDb/Stores/OpenIddictMongoDbScopeStore.cs b/src/OpenIddict.MongoDb/Stores/OpenIddictMongoDbScopeStore.cs
index a1ca929b..b4d3fa51 100644
--- a/src/OpenIddict.MongoDb/Stores/OpenIddictMongoDbScopeStore.cs
+++ b/src/OpenIddict.MongoDb/Stores/OpenIddictMongoDbScopeStore.cs
@@ -7,6 +7,7 @@
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
+using System.Globalization;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text;
@@ -283,6 +284,30 @@ namespace OpenIddict.MongoDb
return new ValueTask(scope.Description);
}
+ ///
+ /// Retrieves the localized descriptions associated with a scope.
+ ///
+ /// The scope.
+ /// The that can be used to abort the operation.
+ ///
+ /// A that can be used to monitor the asynchronous operation,
+ /// whose result returns all the localized descriptions associated with the specified scope.
+ ///
+ public virtual ValueTask> GetDescriptionsAsync([NotNull] TScope scope, CancellationToken cancellationToken)
+ {
+ if (scope == null)
+ {
+ throw new ArgumentNullException(nameof(scope));
+ }
+
+ if (scope.Descriptions == null || scope.Descriptions.Count == 0)
+ {
+ return new ValueTask>(ImmutableDictionary.Create());
+ }
+
+ return new ValueTask>(scope.Descriptions.ToImmutableDictionary());
+ }
+
///
/// Retrieves the display name associated with a scope.
///
@@ -302,6 +327,30 @@ namespace OpenIddict.MongoDb
return new ValueTask(scope.DisplayName);
}
+ ///
+ /// Retrieves the localized display names associated with a scope.
+ ///
+ /// The scope.
+ /// The that can be used to abort the operation.
+ ///
+ /// A that can be used to monitor the asynchronous operation,
+ /// whose result returns all the localized display names associated with the scope.
+ ///
+ public virtual ValueTask> GetDisplayNamesAsync([NotNull] TScope scope, CancellationToken cancellationToken)
+ {
+ if (scope == null)
+ {
+ throw new ArgumentNullException(nameof(scope));
+ }
+
+ if (scope.DisplayNames == null || scope.DisplayNames.Count == 0)
+ {
+ return new ValueTask>(ImmutableDictionary.Create());
+ }
+
+ return new ValueTask>(scope.DisplayNames.ToImmutableDictionary());
+ }
+
///
/// Retrieves the unique identifier associated with a scope.
///
@@ -381,7 +430,7 @@ namespace OpenIddict.MongoDb
throw new ArgumentNullException(nameof(scope));
}
- if (scope.Resources == null || scope.Resources.Length == 0)
+ if (scope.Resources == null || scope.Resources.Count == 0)
{
return new ValueTask>(ImmutableArray.Create());
}
@@ -497,6 +546,46 @@ namespace OpenIddict.MongoDb
return default;
}
+ ///
+ /// Sets the localized descriptions associated with a scope.
+ ///
+ /// The scope.
+ /// The localized descriptions associated with the authorization.
+ /// The that can be used to abort the operation.
+ /// A that can be used to monitor the asynchronous operation.
+ public virtual ValueTask SetDescriptionsAsync([NotNull] TScope scope,
+ [CanBeNull] ImmutableDictionary descriptions, CancellationToken cancellationToken)
+ {
+ if (scope == null)
+ {
+ throw new ArgumentNullException(nameof(scope));
+ }
+
+ scope.Descriptions = descriptions;
+
+ return default;
+ }
+
+ ///
+ /// Sets the localized display names associated with a scope.
+ ///
+ /// The scope.
+ /// The localized display names associated with the scope.
+ /// The that can be used to abort the operation.
+ /// A that can be used to monitor the asynchronous operation.
+ public virtual ValueTask SetDisplayNamesAsync([NotNull] TScope scope,
+ [CanBeNull] ImmutableDictionary names, CancellationToken cancellationToken)
+ {
+ if (scope == null)
+ {
+ throw new ArgumentNullException(nameof(scope));
+ }
+
+ scope.DisplayNames = names;
+
+ return default;
+ }
+
///
/// Sets the display name associated with a scope.
///