Browse Source

Implement localized descriptions/display names support

pull/1029/head
Kévin Chalet 6 years ago
parent
commit
493fa86c0b
  1. 4
      samples/Mvc.Server/Controllers/AuthorizationController.cs
  2. 9
      src/OpenIddict.Abstractions/Descriptors/OpenIddictApplicationDescriptor.cs
  3. 2
      src/OpenIddict.Abstractions/Descriptors/OpenIddictAuthorizationDescriptor.cs
  4. 19
      src/OpenIddict.Abstractions/Descriptors/OpenIddictScopeDescriptor.cs
  5. 39
      src/OpenIddict.Abstractions/Managers/IOpenIddictApplicationManager.cs
  6. 77
      src/OpenIddict.Abstractions/Managers/IOpenIddictScopeManager.cs
  7. 2
      src/OpenIddict.Abstractions/Primitives/OpenIddictParameter.cs
  8. 22
      src/OpenIddict.Abstractions/Stores/IOpenIddictApplicationStore.cs
  9. 43
      src/OpenIddict.Abstractions/Stores/IOpenIddictScopeStore.cs
  10. 108
      src/OpenIddict.Core/Managers/OpenIddictApplicationManager.cs
  11. 205
      src/OpenIddict.Core/Managers/OpenIddictScopeManager.cs
  12. 7
      src/OpenIddict.EntityFramework.Models/OpenIddictEntityFrameworkApplication.cs
  13. 13
      src/OpenIddict.EntityFramework.Models/OpenIddictEntityFrameworkScope.cs
  14. 83
      src/OpenIddict.EntityFramework/Stores/OpenIddictEntityFrameworkApplicationStore.cs
  15. 164
      src/OpenIddict.EntityFramework/Stores/OpenIddictEntityFrameworkScopeStore.cs
  16. 7
      src/OpenIddict.EntityFrameworkCore.Models/OpenIddictEntityFrameworkCoreApplication.cs
  17. 13
      src/OpenIddict.EntityFrameworkCore.Models/OpenIddictEntityFrameworkCoreScope.cs
  18. 83
      src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictEntityFrameworkCoreApplicationStore.cs
  19. 164
      src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictEntityFrameworkCoreScopeStore.cs
  20. 1
      src/OpenIddict.MongoDb.Models/OpenIddict.MongoDb.Models.csproj
  21. 19
      src/OpenIddict.MongoDb.Models/OpenIddictMongoDbApplication.cs
  22. 4
      src/OpenIddict.MongoDb.Models/OpenIddictMongoDbAuthorization.cs
  23. 21
      src/OpenIddict.MongoDb.Models/OpenIddictMongoDbScope.cs
  24. 53
      src/OpenIddict.MongoDb/Stores/OpenIddictMongoDbApplicationStore.cs
  25. 2
      src/OpenIddict.MongoDb/Stores/OpenIddictMongoDbAuthorizationStore.cs
  26. 91
      src/OpenIddict.MongoDb/Stores/OpenIddictMongoDbScopeStore.cs

4
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
});

9
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.
/// </summary>
public virtual string ConsentType { get; set; }
public string ConsentType { get; set; }
/// <summary>
/// Gets or sets the display name
@ -33,6 +34,12 @@ namespace OpenIddict.Abstractions
/// </summary>
public string DisplayName { get; set; }
/// <summary>
/// Gets the localized display names associated with the application.
/// </summary>
public Dictionary<CultureInfo, string> DisplayNames { get; }
= new Dictionary<CultureInfo, string>();
/// <summary>
/// Gets the permissions associated with the application.
/// </summary>

2
src/OpenIddict.Abstractions/Descriptors/OpenIddictAuthorizationDescriptor.cs

@ -38,6 +38,6 @@ namespace OpenIddict.Abstractions
/// <summary>
/// Gets or sets the type of the authorization.
/// </summary>
public virtual string Type { get; set; }
public string Type { get; set; }
}
}

19
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.
/// </summary>
public virtual string Description { get; set; }
public string Description { get; set; }
/// <summary>
/// Gets the localized descriptions associated with the scope.
/// </summary>
public Dictionary<CultureInfo, string> Descriptions { get; } = new Dictionary<CultureInfo, string>();
/// <summary>
/// Gets or sets the display name
/// associated with the scope.
/// </summary>
public virtual string DisplayName { get; set; }
public string DisplayName { get; set; }
/// <summary>
/// Gets the localized display names associated with the scope.
/// </summary>
public Dictionary<CultureInfo, string> DisplayNames { get; } = new Dictionary<CultureInfo, string>();
/// <summary>
/// Gets or sets the unique name
/// associated with the scope.
/// </summary>
public virtual string Name { get; set; }
public string Name { get; set; }
/// <summary>
/// Gets the resources associated with the scope.
/// </summary>
public virtual HashSet<string> Resources { get; } = new HashSet<string>(StringComparer.Ordinal);
public HashSet<string> Resources { get; } = new HashSet<string>(StringComparer.Ordinal);
}
}

39
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
/// </returns>
ValueTask<string> GetDisplayNameAsync([NotNull] object application, CancellationToken cancellationToken = default);
/// <summary>
/// Retrieves the localized display names associated with an application.
/// </summary>
/// <param name="application">The application.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns all the localized display names associated with the application.
/// </returns>
ValueTask<ImmutableDictionary<CultureInfo, string>> GetDisplayNamesAsync([NotNull] object application, CancellationToken cancellationToken = default);
/// <summary>
/// Retrieves the unique identifier associated with an application.
/// </summary>
@ -209,6 +221,33 @@ namespace OpenIddict.Abstractions
/// </returns>
ValueTask<string> GetIdAsync([NotNull] object application, CancellationToken cancellationToken = default);
/// <summary>
/// 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.
/// </summary>
/// <param name="application">The application.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns the matching localized display name associated with the application.
/// </returns>
ValueTask<string> GetLocalizedDisplayNameAsync([NotNull] object application, CancellationToken cancellationToken = default);
/// <summary>
/// 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.
/// </summary>
/// <param name="application">The application.</param>
/// <param name="culture">The culture (typically <see cref="CultureInfo.CurrentUICulture"/>).</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns the matching localized display name associated with the application.
/// </returns>
ValueTask<string> GetLocalizedDisplayNameAsync([NotNull] object application, [NotNull] CultureInfo culture, CancellationToken cancellationToken = default);
/// <summary>
/// Retrieves the permissions associated with an application.
/// </summary>

77
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
/// </returns>
ValueTask<string> GetDescriptionAsync([NotNull] object scope, CancellationToken cancellationToken = default);
/// <summary>
/// Retrieves the localized descriptions associated with an scope.
/// </summary>
/// <param name="scope">The scope.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns all the localized descriptions associated with the scope.
/// </returns>
ValueTask<ImmutableDictionary<CultureInfo, string>> GetDescriptionsAsync([NotNull] object scope, CancellationToken cancellationToken = default);
/// <summary>
/// Retrieves the display name associated with a scope.
/// </summary>
@ -160,6 +172,17 @@ namespace OpenIddict.Abstractions
/// </returns>
ValueTask<string> GetDisplayNameAsync([NotNull] object scope, CancellationToken cancellationToken = default);
/// <summary>
/// Retrieves the localized display names associated with an scope.
/// </summary>
/// <param name="scope">The scope.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns all the localized display names associated with the scope.
/// </returns>
ValueTask<ImmutableDictionary<CultureInfo, string>> GetDisplayNamesAsync([NotNull] object scope, CancellationToken cancellationToken = default);
/// <summary>
/// Retrieves the unique identifier associated with a scope.
/// </summary>
@ -171,6 +194,60 @@ namespace OpenIddict.Abstractions
/// </returns>
ValueTask<string> GetIdAsync([NotNull] object scope, CancellationToken cancellationToken = default);
/// <summary>
/// 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.
/// </summary>
/// <param name="scope">The scope.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns the matching localized description associated with the scope.
/// </returns>
ValueTask<string> GetLocalizedDescriptionAsync([NotNull] object scope, CancellationToken cancellationToken = default);
/// <summary>
/// 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.
/// </summary>
/// <param name="scope">The scope.</param>
/// <param name="culture">The culture (typically <see cref="CultureInfo.CurrentUICulture"/>).</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns the matching localized description associated with the scope.
/// </returns>
ValueTask<string> GetLocalizedDescriptionAsync([NotNull] object scope, [NotNull] CultureInfo culture, CancellationToken cancellationToken = default);
/// <summary>
/// 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.
/// </summary>
/// <param name="scope">The scope.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns the display name associated with the scope.
/// </returns>
ValueTask<string> GetLocalizedDisplayNameAsync([NotNull] object scope, CancellationToken cancellationToken = default);
/// <summary>
/// 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.
/// </summary>
/// <param name="scope">The scope.</param>
/// <param name="culture">The culture (typically <see cref="CultureInfo.CurrentUICulture"/>).</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns the display name associated with the scope.
/// </returns>
ValueTask<string> GetLocalizedDisplayNameAsync([NotNull] object scope, [NotNull] CultureInfo culture, CancellationToken cancellationToken = default);
/// <summary>
/// Retrieves the name associated with a scope.
/// </summary>

2
src/OpenIddict.Abstractions/Primitives/OpenIddictParameter.cs

@ -374,7 +374,7 @@ namespace OpenIddict.Abstractions
return parameters;
}
return ImmutableDictionary.Create<string, OpenIddictParameter>();
return ImmutableDictionary.Create<string, OpenIddictParameter>(StringComparer.Ordinal);
}
/// <summary>

22
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
/// </returns>
ValueTask<string> GetDisplayNameAsync([NotNull] TApplication application, CancellationToken cancellationToken);
/// <summary>
/// Retrieves the localized display names associated with an application.
/// </summary>
/// <param name="application">The application.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns all the localized display names associated with the application.
/// </returns>
ValueTask<ImmutableDictionary<CultureInfo, string>> GetDisplayNamesAsync([NotNull] TApplication application, CancellationToken cancellationToken);
/// <summary>
/// Retrieves the unique identifier associated with an application.
/// </summary>
@ -315,6 +327,16 @@ namespace OpenIddict.Abstractions
/// <returns>A <see cref="ValueTask"/> that can be used to monitor the asynchronous operation.</returns>
ValueTask SetDisplayNameAsync([NotNull] TApplication application, [CanBeNull] string name, CancellationToken cancellationToken);
/// <summary>
/// Sets the localized display names associated with an application.
/// </summary>
/// <param name="application">The application.</param>
/// <param name="names">The localized display names associated with the application.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>A <see cref="ValueTask"/> that can be used to monitor the asynchronous operation.</returns>
ValueTask SetDisplayNamesAsync([NotNull] TApplication application,
[CanBeNull] ImmutableDictionary<CultureInfo, string> names, CancellationToken cancellationToken);
/// <summary>
/// Sets the permissions associated with an application.
/// </summary>

43
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
/// </returns>
ValueTask<string> GetDescriptionAsync([NotNull] TScope scope, CancellationToken cancellationToken);
/// <summary>
/// Retrieves the localized descriptions associated with a scope.
/// </summary>
/// <param name="scope">The scope.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns all the localized descriptions associated with the specified scope.
/// </returns>
ValueTask<ImmutableDictionary<CultureInfo, string>> GetDescriptionsAsync([NotNull] TScope scope, CancellationToken cancellationToken);
/// <summary>
/// Retrieves the display name associated with a scope.
/// </summary>
@ -135,6 +147,17 @@ namespace OpenIddict.Abstractions
/// </returns>
ValueTask<string> GetDisplayNameAsync([NotNull] TScope scope, CancellationToken cancellationToken);
/// <summary>
/// Retrieves the localized display names associated with a scope.
/// </summary>
/// <param name="scope">The scope.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns all the localized display names associated with the scope.
/// </returns>
ValueTask<ImmutableDictionary<CultureInfo, string>> GetDisplayNamesAsync([NotNull] TScope scope, CancellationToken cancellationToken);
/// <summary>
/// Retrieves the unique identifier associated with a scope.
/// </summary>
@ -220,6 +243,16 @@ namespace OpenIddict.Abstractions
/// <returns>A <see cref="ValueTask"/> that can be used to monitor the asynchronous operation.</returns>
ValueTask SetDescriptionAsync([NotNull] TScope scope, [CanBeNull] string description, CancellationToken cancellationToken);
/// <summary>
/// Sets the localized descriptions associated with a scope.
/// </summary>
/// <param name="scope">The scope.</param>
/// <param name="descriptions">The localized descriptions associated with the authorization.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>A <see cref="ValueTask"/> that can be used to monitor the asynchronous operation.</returns>
ValueTask SetDescriptionsAsync([NotNull] TScope scope,
[CanBeNull] ImmutableDictionary<CultureInfo, string> descriptions, CancellationToken cancellationToken);
/// <summary>
/// Sets the display name associated with a scope.
/// </summary>
@ -229,6 +262,16 @@ namespace OpenIddict.Abstractions
/// <returns>A <see cref="ValueTask"/> that can be used to monitor the asynchronous operation.</returns>
ValueTask SetDisplayNameAsync([NotNull] TScope scope, [CanBeNull] string name, CancellationToken cancellationToken);
/// <summary>
/// Sets the localized display names associated with a scope.
/// </summary>
/// <param name="scope">The scope.</param>
/// <param name="names">The localized display names associated with the scope.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>A <see cref="ValueTask"/> that can be used to monitor the asynchronous operation.</returns>
ValueTask SetDisplayNamesAsync([NotNull] TScope scope,
[CanBeNull] ImmutableDictionary<CultureInfo, string> names, CancellationToken cancellationToken);
/// <summary>
/// Sets the name associated with a scope.
/// </summary>

108
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);
}
/// <summary>
/// Retrieves the localized display names associated with an application.
/// </summary>
/// <param name="application">The application.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns all the localized display names associated with the application.
/// </returns>
public virtual async ValueTask<ImmutableDictionary<CultureInfo, string>> 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<CultureInfo, string>();
}
return names;
}
/// <summary>
/// Retrieves the unique identifier associated with an application.
/// </summary>
@ -570,6 +597,67 @@ namespace OpenIddict.Core
return Store.GetIdAsync(application, cancellationToken);
}
/// <summary>
/// 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.
/// </summary>
/// <param name="application">The application.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns the matching localized display name associated with the application.
/// </returns>
public virtual ValueTask<string> GetLocalizedDisplayNameAsync(
[NotNull] TApplication application, CancellationToken cancellationToken = default)
=> GetLocalizedDisplayNameAsync(application, CultureInfo.CurrentUICulture, cancellationToken);
/// <summary>
/// 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.
/// </summary>
/// <param name="application">The application.</param>
/// <param name="culture">The culture (typically <see cref="CultureInfo.CurrentUICulture"/>).</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns the matching localized display name associated with the application.
/// </returns>
public virtual async ValueTask<string> 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);
}
/// <summary>
/// Retrieves the permissions associated with an application.
/// </summary>
@ -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);
}
/// <summary>
@ -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<string> IOpenIddictApplicationManager.GetDisplayNameAsync(object application, CancellationToken cancellationToken)
=> GetDisplayNameAsync((TApplication) application, cancellationToken);
ValueTask<ImmutableDictionary<CultureInfo, string>> IOpenIddictApplicationManager.GetDisplayNamesAsync(object application, CancellationToken cancellationToken)
=> GetDisplayNamesAsync((TApplication) application, cancellationToken);
ValueTask<string> IOpenIddictApplicationManager.GetIdAsync(object application, CancellationToken cancellationToken)
=> GetIdAsync((TApplication) application, cancellationToken);
ValueTask<string> IOpenIddictApplicationManager.GetLocalizedDisplayNameAsync(object application, CancellationToken cancellationToken)
=> GetLocalizedDisplayNameAsync((TApplication) application, cancellationToken);
ValueTask<string> IOpenIddictApplicationManager.GetLocalizedDisplayNameAsync(object application, CultureInfo culture, CancellationToken cancellationToken)
=> GetLocalizedDisplayNameAsync((TApplication) application, culture, cancellationToken);
ValueTask<ImmutableArray<string>> IOpenIddictApplicationManager.GetPermissionsAsync(object application, CancellationToken cancellationToken)
=> GetPermissionsAsync((TApplication) application, cancellationToken);

205
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);
}
/// <summary>
/// Retrieves the localized descriptions associated with an scope.
/// </summary>
/// <param name="scope">The scope.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns all the localized descriptions associated with the scope.
/// </returns>
public virtual async ValueTask<ImmutableDictionary<CultureInfo, string>> 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<CultureInfo, string>();
}
return descriptions;
}
/// <summary>
/// Retrieves the display name associated with a scope.
/// </summary>
@ -439,6 +466,32 @@ namespace OpenIddict.Core
return Store.GetDisplayNameAsync(scope, cancellationToken);
}
/// <summary>
/// Retrieves the localized display names associated with an scope.
/// </summary>
/// <param name="scope">The scope.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns all the localized display names associated with the scope.
/// </returns>
public virtual async ValueTask<ImmutableDictionary<CultureInfo, string>> 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<CultureInfo, string>();
}
return names;
}
/// <summary>
/// Retrieves the unique identifier associated with a scope.
/// </summary>
@ -458,6 +511,126 @@ namespace OpenIddict.Core
return Store.GetIdAsync(scope, cancellationToken);
}
/// <summary>
/// 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.
/// </summary>
/// <param name="scope">The scope.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns the matching display name associated with the scope.
/// </returns>
public virtual ValueTask<string> GetLocalizedDisplayNameAsync([NotNull] TScope scope, CancellationToken cancellationToken = default)
=> GetLocalizedDisplayNameAsync(scope, CultureInfo.CurrentUICulture, cancellationToken);
/// <summary>
/// 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.
/// </summary>
/// <param name="scope">The scope.</param>
/// <param name="culture">The culture (typically <see cref="CultureInfo.CurrentUICulture"/>).</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns the matching display name associated with the scope.
/// </returns>
public virtual async ValueTask<string> 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);
}
/// <summary>
/// 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.
/// </summary>
/// <param name="scope">The scope.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns the matching localized description associated with the scope.
/// </returns>
public virtual ValueTask<string> GetLocalizedDescriptionAsync([NotNull] TScope scope, CancellationToken cancellationToken = default)
=> GetLocalizedDescriptionAsync(scope, CultureInfo.CurrentUICulture, cancellationToken);
/// <summary>
/// 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.
/// </summary>
/// <param name="scope">The scope.</param>
/// <param name="culture">The culture (typically <see cref="CultureInfo.CurrentUICulture"/>).</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns the matching localized description associated with the scope.
/// </returns>
public virtual async ValueTask<string> 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);
}
/// <summary>
/// Retrieves the name associated with a scope.
/// </summary>
@ -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);
}
}
/// <summary>
@ -784,12 +971,30 @@ namespace OpenIddict.Core
ValueTask<string> IOpenIddictScopeManager.GetDescriptionAsync(object scope, CancellationToken cancellationToken)
=> GetDescriptionAsync((TScope) scope, cancellationToken);
ValueTask<ImmutableDictionary<CultureInfo, string>> IOpenIddictScopeManager.GetDescriptionsAsync(object scope, CancellationToken cancellationToken)
=> GetDescriptionsAsync((TScope) scope, cancellationToken);
ValueTask<string> IOpenIddictScopeManager.GetDisplayNameAsync(object scope, CancellationToken cancellationToken)
=> GetDisplayNameAsync((TScope) scope, cancellationToken);
ValueTask<ImmutableDictionary<CultureInfo, string>> IOpenIddictScopeManager.GetDisplayNamesAsync(object scope, CancellationToken cancellationToken)
=> GetDisplayNamesAsync((TScope) scope, cancellationToken);
ValueTask<string> IOpenIddictScopeManager.GetIdAsync(object scope, CancellationToken cancellationToken)
=> GetIdAsync((TScope) scope, cancellationToken);
ValueTask<string> IOpenIddictScopeManager.GetLocalizedDescriptionAsync(object scope, CancellationToken cancellationToken)
=> GetLocalizedDescriptionAsync((TScope) scope, cancellationToken);
ValueTask<string> IOpenIddictScopeManager.GetLocalizedDescriptionAsync(object scope, CultureInfo culture, CancellationToken cancellationToken)
=> GetLocalizedDescriptionAsync((TScope) scope, culture, cancellationToken);
ValueTask<string> IOpenIddictScopeManager.GetLocalizedDisplayNameAsync(object scope, CancellationToken cancellationToken)
=> GetLocalizedDisplayNameAsync((TScope) scope, cancellationToken);
ValueTask<string> IOpenIddictScopeManager.GetLocalizedDisplayNameAsync(object scope, CultureInfo culture, CancellationToken cancellationToken)
=> GetLocalizedDisplayNameAsync((TScope) scope, culture, cancellationToken);
ValueTask<string> IOpenIddictScopeManager.GetNameAsync(object scope, CancellationToken cancellationToken)
=> GetNameAsync((TScope) scope, cancellationToken);

7
src/OpenIddict.EntityFramework.Models/OpenIddictEntityFrameworkApplication.cs

@ -63,6 +63,13 @@ namespace OpenIddict.EntityFramework.Models
/// </summary>
public virtual string DisplayName { get; set; }
/// <summary>
/// Gets or sets the localized display names
/// associated with the current application,
/// serialized as a JSON object.
/// </summary>
public virtual string DisplayNames { get; set; }
/// <summary>
/// Gets or sets the unique identifier
/// associated with the current application.

13
src/OpenIddict.EntityFramework.Models/OpenIddictEntityFrameworkScope.cs

@ -38,12 +38,25 @@ namespace OpenIddict.EntityFramework.Models
/// </summary>
public virtual string Description { get; set; }
/// <summary>
/// Gets or sets the localized public descriptions associated
/// with the current scope, serialized as a JSON object.
/// </summary>
public virtual string Descriptions { get; set; }
/// <summary>
/// Gets or sets the display name
/// associated with the current scope.
/// </summary>
public virtual string DisplayName { get; set; }
/// <summary>
/// Gets or sets the localized display names
/// associated with the current application,
/// serialized as a JSON object.
/// </summary>
public virtual string DisplayNames { get; set; }
/// <summary>
/// Gets or sets the unique identifier
/// associated with the current scope.

83
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<string>(application.DisplayName);
}
/// <summary>
/// Retrieves the localized display names associated with an application.
/// </summary>
/// <param name="application">The application.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns all the localized display names associated with the application.
/// </returns>
public virtual ValueTask<ImmutableDictionary<CultureInfo, string>> GetDisplayNamesAsync([NotNull] TApplication application, CancellationToken cancellationToken)
{
if (application == null)
{
throw new ArgumentNullException(nameof(application));
}
if (string.IsNullOrEmpty(application.DisplayNames))
{
return new ValueTask<ImmutableDictionary<CultureInfo, string>>(ImmutableDictionary.Create<CultureInfo, string>());
}
// 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<Dictionary<string, string>>(application.DisplayNames)
.ToImmutableDictionary(name => CultureInfo.GetCultureInfo(name.Key), name => name.Value);
});
return new ValueTask<ImmutableDictionary<CultureInfo, string>>(names);
}
/// <summary>
/// Retrieves the unique identifier associated with an application.
/// </summary>
@ -852,6 +890,51 @@ namespace OpenIddict.EntityFramework
return default;
}
/// <summary>
/// Sets the localized display names associated with an application.
/// </summary>
/// <param name="application">The application.</param>
/// <param name="names">The localized display names associated with the application.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>A <see cref="ValueTask"/> that can be used to monitor the asynchronous operation.</returns>
public virtual ValueTask SetDisplayNamesAsync([NotNull] TApplication application,
[CanBeNull] ImmutableDictionary<CultureInfo, string> 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;
}
/// <summary>
/// Sets the permissions associated with an application.
/// </summary>

164
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<string>(scope.Description);
}
/// <summary>
/// Retrieves the localized descriptions associated with a scope.
/// </summary>
/// <param name="scope">The scope.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns all the localized descriptions associated with the specified scope.
/// </returns>
public virtual ValueTask<ImmutableDictionary<CultureInfo, string>> GetDescriptionsAsync([NotNull] TScope scope, CancellationToken cancellationToken)
{
if (scope == null)
{
throw new ArgumentNullException(nameof(scope));
}
if (string.IsNullOrEmpty(scope.Descriptions))
{
return new ValueTask<ImmutableDictionary<CultureInfo, string>>(ImmutableDictionary.Create<CultureInfo, string>());
}
// 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<Dictionary<string, string>>(scope.Descriptions)
.ToImmutableDictionary(description => CultureInfo.GetCultureInfo(description.Key), description => description.Value);
});
return new ValueTask<ImmutableDictionary<CultureInfo, string>>(descriptions);
}
/// <summary>
/// Retrieves the display name associated with a scope.
/// </summary>
@ -330,6 +368,42 @@ namespace OpenIddict.EntityFramework
return new ValueTask<string>(scope.DisplayName);
}
/// <summary>
/// Retrieves the localized display names associated with a scope.
/// </summary>
/// <param name="scope">The scope.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns all the localized display names associated with the scope.
/// </returns>
public virtual ValueTask<ImmutableDictionary<CultureInfo, string>> GetDisplayNamesAsync([NotNull] TScope scope, CancellationToken cancellationToken)
{
if (scope == null)
{
throw new ArgumentNullException(nameof(scope));
}
if (string.IsNullOrEmpty(scope.DisplayNames))
{
return new ValueTask<ImmutableDictionary<CultureInfo, string>>(ImmutableDictionary.Create<CultureInfo, string>());
}
// 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<Dictionary<string, string>>(scope.DisplayNames)
.ToImmutableDictionary(name => CultureInfo.GetCultureInfo(name.Key), name => name.Value);
});
return new ValueTask<ImmutableDictionary<CultureInfo, string>>(names);
}
/// <summary>
/// Retrieves the unique identifier associated with a scope.
/// </summary>
@ -529,6 +603,51 @@ namespace OpenIddict.EntityFramework
return default;
}
/// <summary>
/// Sets the localized descriptions associated with a scope.
/// </summary>
/// <param name="scope">The scope.</param>
/// <param name="descriptions">The localized descriptions associated with the authorization.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>A <see cref="ValueTask"/> that can be used to monitor the asynchronous operation.</returns>
public virtual ValueTask SetDescriptionsAsync([NotNull] TScope scope,
[CanBeNull] ImmutableDictionary<CultureInfo, string> 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;
}
/// <summary>
/// Sets the display name associated with a scope.
/// </summary>
@ -548,6 +667,51 @@ namespace OpenIddict.EntityFramework
return default;
}
/// <summary>
/// Sets the localized display names associated with a scope.
/// </summary>
/// <param name="scope">The scope.</param>
/// <param name="names">The localized display names associated with the scope.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>A <see cref="ValueTask"/> that can be used to monitor the asynchronous operation.</returns>
public virtual ValueTask SetDisplayNamesAsync([NotNull] TScope scope,
[CanBeNull] ImmutableDictionary<CultureInfo, string> 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;
}
/// <summary>
/// Sets the name associated with a scope.
/// </summary>

7
src/OpenIddict.EntityFrameworkCore.Models/OpenIddictEntityFrameworkCoreApplication.cs

@ -71,6 +71,13 @@ namespace OpenIddict.EntityFrameworkCore.Models
/// </summary>
public virtual string DisplayName { get; set; }
/// <summary>
/// Gets or sets the localized display names
/// associated with the current application,
/// serialized as a JSON object.
/// </summary>
public virtual string DisplayNames { get; set; }
/// <summary>
/// Gets or sets the unique identifier
/// associated with the current application.

13
src/OpenIddict.EntityFrameworkCore.Models/OpenIddictEntityFrameworkCoreScope.cs

@ -38,12 +38,25 @@ namespace OpenIddict.EntityFrameworkCore.Models
/// </summary>
public virtual string Description { get; set; }
/// <summary>
/// Gets or sets the localized public descriptions associated
/// with the current scope, serialized as a JSON object.
/// </summary>
public virtual string Descriptions { get; set; }
/// <summary>
/// Gets or sets the display name
/// associated with the current scope.
/// </summary>
public virtual string DisplayName { get; set; }
/// <summary>
/// Gets or sets the localized display names
/// associated with the current application,
/// serialized as a JSON object.
/// </summary>
public virtual string DisplayNames { get; set; }
/// <summary>
/// Gets or sets the unique identifier
/// associated with the current scope.

83
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<string>(application.DisplayName);
}
/// <summary>
/// Retrieves the localized display names associated with an application.
/// </summary>
/// <param name="application">The application.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns all the localized display names associated with the application.
/// </returns>
public virtual ValueTask<ImmutableDictionary<CultureInfo, string>> GetDisplayNamesAsync([NotNull] TApplication application, CancellationToken cancellationToken)
{
if (application == null)
{
throw new ArgumentNullException(nameof(application));
}
if (string.IsNullOrEmpty(application.DisplayNames))
{
return new ValueTask<ImmutableDictionary<CultureInfo, string>>(ImmutableDictionary.Create<CultureInfo, string>());
}
// 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<Dictionary<string, string>>(application.DisplayNames)
.ToImmutableDictionary(name => CultureInfo.GetCultureInfo(name.Key), name => name.Value);
});
return new ValueTask<ImmutableDictionary<CultureInfo, string>>(names);
}
/// <summary>
/// Retrieves the unique identifier associated with an application.
/// </summary>
@ -896,6 +934,51 @@ namespace OpenIddict.EntityFrameworkCore
return default;
}
/// <summary>
/// Sets the localized display names associated with an application.
/// </summary>
/// <param name="application">The application.</param>
/// <param name="names">The localized display names associated with the application.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>A <see cref="ValueTask"/> that can be used to monitor the asynchronous operation.</returns>
public virtual ValueTask SetDisplayNamesAsync([NotNull] TApplication application,
[CanBeNull] ImmutableDictionary<CultureInfo, string> 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;
}
/// <summary>
/// Sets the permissions associated with an application.
/// </summary>

164
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<string>(scope.Description);
}
/// <summary>
/// Retrieves the localized descriptions associated with a scope.
/// </summary>
/// <param name="scope">The scope.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns all the localized descriptions associated with the specified scope.
/// </returns>
public virtual ValueTask<ImmutableDictionary<CultureInfo, string>> GetDescriptionsAsync([NotNull] TScope scope, CancellationToken cancellationToken)
{
if (scope == null)
{
throw new ArgumentNullException(nameof(scope));
}
if (string.IsNullOrEmpty(scope.Descriptions))
{
return new ValueTask<ImmutableDictionary<CultureInfo, string>>(ImmutableDictionary.Create<CultureInfo, string>());
}
// 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<Dictionary<string, string>>(scope.Descriptions)
.ToImmutableDictionary(description => CultureInfo.GetCultureInfo(description.Key), description => description.Value);
});
return new ValueTask<ImmutableDictionary<CultureInfo, string>>(descriptions);
}
/// <summary>
/// Retrieves the display name associated with a scope.
/// </summary>
@ -346,6 +384,42 @@ namespace OpenIddict.EntityFrameworkCore
return new ValueTask<string>(scope.DisplayName);
}
/// <summary>
/// Retrieves the localized display names associated with a scope.
/// </summary>
/// <param name="scope">The scope.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns all the localized display names associated with the scope.
/// </returns>
public virtual ValueTask<ImmutableDictionary<CultureInfo, string>> GetDisplayNamesAsync([NotNull] TScope scope, CancellationToken cancellationToken)
{
if (scope == null)
{
throw new ArgumentNullException(nameof(scope));
}
if (string.IsNullOrEmpty(scope.DisplayNames))
{
return new ValueTask<ImmutableDictionary<CultureInfo, string>>(ImmutableDictionary.Create<CultureInfo, string>());
}
// 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<Dictionary<string, string>>(scope.DisplayNames)
.ToImmutableDictionary(name => CultureInfo.GetCultureInfo(name.Key), name => name.Value);
});
return new ValueTask<ImmutableDictionary<CultureInfo, string>>(names);
}
/// <summary>
/// Retrieves the unique identifier associated with a scope.
/// </summary>
@ -545,6 +619,51 @@ namespace OpenIddict.EntityFrameworkCore
return default;
}
/// <summary>
/// Sets the localized descriptions associated with a scope.
/// </summary>
/// <param name="scope">The scope.</param>
/// <param name="descriptions">The localized descriptions associated with the authorization.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>A <see cref="ValueTask"/> that can be used to monitor the asynchronous operation.</returns>
public virtual ValueTask SetDescriptionsAsync([NotNull] TScope scope,
[CanBeNull] ImmutableDictionary<CultureInfo, string> 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;
}
/// <summary>
/// Sets the display name associated with a scope.
/// </summary>
@ -564,6 +683,51 @@ namespace OpenIddict.EntityFrameworkCore
return default;
}
/// <summary>
/// Sets the localized display names associated with a scope.
/// </summary>
/// <param name="scope">The scope.</param>
/// <param name="names">The localized display names associated with the scope.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>A <see cref="ValueTask"/> that can be used to monitor the asynchronous operation.</returns>
public virtual ValueTask SetDisplayNamesAsync([NotNull] TScope scope,
[CanBeNull] ImmutableDictionary<CultureInfo, string> 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;
}
/// <summary>
/// Sets the name associated with a scope.
/// </summary>

1
src/OpenIddict.MongoDb.Models/OpenIddict.MongoDb.Models.csproj

@ -13,6 +13,7 @@
<ItemGroup>
<PackageReference Include="MongoDB.Bson" />
<PackageReference Include="System.Collections.Immutable" />
</ItemGroup>
</Project>

19
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; }
/// <summary>
/// Gets or sets the localized display names
/// associated with the current application.
/// </summary>
[BsonElement("display_names"), BsonIgnoreIfNull]
public virtual IReadOnlyDictionary<CultureInfo, string> DisplayNames { get; set; }
= ImmutableDictionary.Create<CultureInfo, string>();
/// <summary>
/// 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.
/// </summary>
[BsonElement("permissions"), BsonIgnoreIfDefault]
public virtual string[] Permissions { get; set; } = Array.Empty<string>();
public virtual IReadOnlyList<string> Permissions { get; set; } = ImmutableArray.Create<string>();
/// <summary>
/// Gets or sets the logout callback URLs associated with the current application.
/// </summary>
[BsonElement("post_logout_redirect_uris"), BsonIgnoreIfDefault]
public virtual string[] PostLogoutRedirectUris { get; set; } = Array.Empty<string>();
public virtual IReadOnlyList<string> PostLogoutRedirectUris { get; set; } = ImmutableArray.Create<string>();
/// <summary>
/// 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.
/// </summary>
[BsonElement("redirect_uris"), BsonIgnoreIfDefault]
public virtual string[] RedirectUris { get; set; } = Array.Empty<string>();
public virtual IReadOnlyList<string> RedirectUris { get; set; } = ImmutableArray.Create<string>();
/// <summary>
/// Gets or sets the requirements associated with the current application.
/// </summary>
[BsonElement("requirements"), BsonIgnoreIfDefault]
public virtual string[] Requirements { get; set; } = Array.Empty<string>();
public virtual IReadOnlyList<string> Requirements { get; set; } = ImmutableArray.Create<string>();
/// <summary>
/// Gets or sets the application type

4
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.
/// </summary>
[BsonElement("scopes"), BsonIgnoreIfDefault]
public virtual string[] Scopes { get; set; } = Array.Empty<string>();
public virtual IReadOnlyList<string> Scopes { get; set; } = ImmutableArray.Create<string>();
/// <summary>
/// Gets or sets the status of the current authorization.

21
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; }
/// <summary>
/// Gets or sets the localized public descriptions
/// associated with the current scope.
/// </summary>
[BsonElement("descriptions"), BsonIgnoreIfNull]
public virtual IReadOnlyDictionary<CultureInfo, string> Descriptions { get; set; }
= ImmutableDictionary.Create<CultureInfo, string>();
/// <summary>
/// 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; }
/// <summary>
/// Gets or sets the localized display names
/// associated with the current scope.
/// </summary>
[BsonElement("display_names"), BsonIgnoreIfNull]
public virtual IReadOnlyDictionary<CultureInfo, string> DisplayNames { get; set; }
= ImmutableDictionary.Create<CultureInfo, string>();
/// <summary>
/// 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.
/// </summary>
[BsonElement("resources"), BsonIgnoreIfDefault]
public virtual string[] Resources { get; set; } = Array.Empty<string>();
public virtual IReadOnlyList<string> Resources { get; set; } = ImmutableArray.Create<string>();
}
}

53
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<string>(application.DisplayName);
}
/// <summary>
/// Retrieves the localized display names associated with an application.
/// </summary>
/// <param name="application">The application.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns all the localized display names associated with the application.
/// </returns>
public virtual ValueTask<ImmutableDictionary<CultureInfo, string>> 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<CultureInfo, string>>(ImmutableDictionary.Create<CultureInfo, string>());
}
return new ValueTask<ImmutableDictionary<CultureInfo, string>>(application.DisplayNames.ToImmutableDictionary());
}
/// <summary>
/// Retrieves the unique identifier associated with an application.
/// </summary>
@ -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<string>>(ImmutableArray.Create<string>());
}
@ -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<string>>(ImmutableArray.Create<string>());
}
@ -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<string>>(ImmutableArray.Create<string>());
}
@ -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<string>>(ImmutableArray.Create<string>());
}
@ -704,6 +729,26 @@ namespace OpenIddict.MongoDb
return default;
}
/// <summary>
/// Sets the localized display names associated with an application.
/// </summary>
/// <param name="application">The application.</param>
/// <param name="names">The localized display names associated with the application.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>A <see cref="ValueTask"/> that can be used to monitor the asynchronous operation.</returns>
public virtual ValueTask SetDisplayNamesAsync([NotNull] TApplication application,
[CanBeNull] ImmutableDictionary<CultureInfo, string> names, CancellationToken cancellationToken)
{
if (application == null)
{
throw new ArgumentNullException(nameof(application));
}
application.DisplayNames = names;
return default;
}
/// <summary>
/// Sets the permissions associated with an application.
/// </summary>

2
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<string>>(ImmutableArray.Create<string>());
}

91
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<string>(scope.Description);
}
/// <summary>
/// Retrieves the localized descriptions associated with a scope.
/// </summary>
/// <param name="scope">The scope.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns all the localized descriptions associated with the specified scope.
/// </returns>
public virtual ValueTask<ImmutableDictionary<CultureInfo, string>> 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<CultureInfo, string>>(ImmutableDictionary.Create<CultureInfo, string>());
}
return new ValueTask<ImmutableDictionary<CultureInfo, string>>(scope.Descriptions.ToImmutableDictionary());
}
/// <summary>
/// Retrieves the display name associated with a scope.
/// </summary>
@ -302,6 +327,30 @@ namespace OpenIddict.MongoDb
return new ValueTask<string>(scope.DisplayName);
}
/// <summary>
/// Retrieves the localized display names associated with a scope.
/// </summary>
/// <param name="scope">The scope.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns all the localized display names associated with the scope.
/// </returns>
public virtual ValueTask<ImmutableDictionary<CultureInfo, string>> 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<CultureInfo, string>>(ImmutableDictionary.Create<CultureInfo, string>());
}
return new ValueTask<ImmutableDictionary<CultureInfo, string>>(scope.DisplayNames.ToImmutableDictionary());
}
/// <summary>
/// Retrieves the unique identifier associated with a scope.
/// </summary>
@ -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<string>>(ImmutableArray.Create<string>());
}
@ -497,6 +546,46 @@ namespace OpenIddict.MongoDb
return default;
}
/// <summary>
/// Sets the localized descriptions associated with a scope.
/// </summary>
/// <param name="scope">The scope.</param>
/// <param name="descriptions">The localized descriptions associated with the authorization.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>A <see cref="ValueTask"/> that can be used to monitor the asynchronous operation.</returns>
public virtual ValueTask SetDescriptionsAsync([NotNull] TScope scope,
[CanBeNull] ImmutableDictionary<CultureInfo, string> descriptions, CancellationToken cancellationToken)
{
if (scope == null)
{
throw new ArgumentNullException(nameof(scope));
}
scope.Descriptions = descriptions;
return default;
}
/// <summary>
/// Sets the localized display names associated with a scope.
/// </summary>
/// <param name="scope">The scope.</param>
/// <param name="names">The localized display names associated with the scope.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>A <see cref="ValueTask"/> that can be used to monitor the asynchronous operation.</returns>
public virtual ValueTask SetDisplayNamesAsync([NotNull] TScope scope,
[CanBeNull] ImmutableDictionary<CultureInfo, string> names, CancellationToken cancellationToken)
{
if (scope == null)
{
throw new ArgumentNullException(nameof(scope));
}
scope.DisplayNames = names;
return default;
}
/// <summary>
/// Sets the display name associated with a scope.
/// </summary>

Loading…
Cancel
Save