Browse Source

Merge branch 'dev' into pr/15878

pull/15878/head
maliming 4 years ago
parent
commit
714ffd8909
No known key found for this signature in database GPG Key ID: A646B9CB645ECEA4
  1. 2
      abp_io/AbpIoLocalization/AbpIoLocalization/Commercial/Localization/Resources/en.json
  2. 59
      docs/en/Modules/OpenIddict.md
  3. 36
      docs/en/Multi-Tenancy.md
  4. 1
      docs/en/UI/Angular/List-Service.md
  5. 5
      framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Auth/LoginInfo.cs
  6. 5
      framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/LoginInfoCommand.cs
  7. 4
      framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/Steps/ChangeThemeStep.cs
  8. 2
      framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Templates/App/AppTemplateBase.cs
  9. 41
      framework/src/Volo.Abp.MultiLingualObjects/Volo.Abp.MultiLingualObjects.csproj
  10. 24
      framework/src/Volo.Abp.MultiLingualObjects/Volo/Abp/MultiLingualObjects/IMultiLingualObjectManager.cs
  11. 123
      framework/src/Volo.Abp.MultiLingualObjects/Volo/Abp/MultiLingualObjects/MultiLingualObjectManager.cs
  12. 19
      framework/src/Volo.Abp.UI.Navigation/Volo/Abp/Ui/Navigation/ApplicationMenu.cs
  13. 50
      framework/src/Volo.Abp.UI.Navigation/Volo/Abp/Ui/Navigation/ApplicationMenuExtensions.cs
  14. 67
      framework/src/Volo.Abp.UI.Navigation/Volo/Abp/Ui/Navigation/ApplicationMenuGroup.cs
  15. 36
      framework/src/Volo.Abp.UI.Navigation/Volo/Abp/Ui/Navigation/ApplicationMenuGroupList.cs
  16. 7
      framework/src/Volo.Abp.UI.Navigation/Volo/Abp/Ui/Navigation/ApplicationMenuItem.cs
  17. 9
      framework/src/Volo.Abp.UI.Navigation/Volo/Abp/Ui/Navigation/IHasMenuGroups.cs
  18. 20
      framework/src/Volo.Abp.UI.Navigation/Volo/Abp/Ui/Navigation/MenuManager.cs
  19. 27
      framework/test/Volo.Abp.MultiLingualObjects.Tests/Volo.Abp.MultiLingualObjects.Tests.csproj
  20. 53
      framework/test/Volo.Abp.MultiLingualObjects.Tests/Volo/Abp/MultiLingualObjects/AbpMultiLingualObjectsTestModule.cs
  21. 135
      framework/test/Volo.Abp.MultiLingualObjects.Tests/Volo/Abp/MultiLingualObjects/MultiLingualObjectManager_Tests.cs
  22. 23
      framework/test/Volo.Abp.MultiLingualObjects.Tests/Volo/Abp/MultiLingualObjects/MultiLingualObjectTestProfile.cs
  23. 38
      framework/test/Volo.Abp.MultiLingualObjects.Tests/Volo/Abp/MultiLingualObjects/TestObjects/MultiLingualBook.cs
  24. 24
      framework/test/Volo.Abp.MultiLingualObjects.Tests/Volo/Abp/MultiLingualObjects/TestObjects/MultiLingualBookDto.cs
  25. 16
      framework/test/Volo.Abp.MultiLingualObjects.Tests/Volo/Abp/MultiLingualObjects/TestObjects/MultiLingualBookTranslation.cs
  26. 1
      framework/test/Volo.Abp.UI.Navigation.Tests/Volo/Abp/Ui/Navigation/AbpUiNavigationTestModule.cs
  27. 46
      framework/test/Volo.Abp.UI.Navigation.Tests/Volo/Abp/Ui/Navigation/MenuManager_Tests.cs
  28. 2
      modules/cms-kit/host/Volo.CmsKit.IdentityServer/package.json
  29. 17
      modules/openiddict/app/OpenIddict.Demo.Server/Migrations/20230307054116_Initial.Designer.cs
  30. 5
      modules/openiddict/app/OpenIddict.Demo.Server/Migrations/20230307054116_Initial.cs
  31. 15
      modules/openiddict/app/OpenIddict.Demo.Server/Migrations/ServerDbContextModelSnapshot.cs
  32. 3
      modules/openiddict/src/Volo.Abp.OpenIddict.AspNetCore/Volo/Abp/OpenIddict/AbpOpenIddictAspNetCoreModule.cs
  33. 9
      modules/openiddict/src/Volo.Abp.OpenIddict.AspNetCore/Volo/Abp/OpenIddict/WildcardDomains/AbpOpenIddictWildcardDomainBase.cs
  34. 37
      modules/openiddict/src/Volo.Abp.OpenIddict.AspNetCore/Volo/Abp/OpenIddict/WildcardDomains/AbpValidateAuthorizedParty.cs
  35. 2
      modules/openiddict/src/Volo.Abp.OpenIddict.AspNetCore/Volo/Abp/OpenIddict/WildcardDomains/AbpValidateClientPostLogoutRedirectUri.cs
  36. 2
      modules/openiddict/src/Volo.Abp.OpenIddict.AspNetCore/Volo/Abp/OpenIddict/WildcardDomains/AbpValidateClientRedirectUri.cs
  37. 2
      modules/openiddict/src/Volo.Abp.OpenIddict.AspNetCore/Volo/Abp/OpenIddict/WildcardDomains/AbpValidatePostLogoutRedirectUriParameter.cs
  38. 2
      modules/openiddict/src/Volo.Abp.OpenIddict.AspNetCore/Volo/Abp/OpenIddict/WildcardDomains/AbpValidateRedirectUriParameter.cs
  39. 2
      npm/ng-packs/packages/theme-shared/src/lib/components/index.ts
  40. BIN
      source-code/Volo.Abp.Account.SourceCode/Volo.Abp.Account.SourceCode.zip
  41. BIN
      source-code/Volo.Abp.AuditLogging.SourceCode/Volo.Abp.AuditLogging.SourceCode.zip
  42. BIN
      source-code/Volo.Abp.BackgroundJobs.SourceCode/Volo.Abp.BackgroundJobs.SourceCode.zip
  43. BIN
      source-code/Volo.Abp.BasicTheme.SourceCode/Volo.Abp.BasicTheme.SourceCode.zip
  44. BIN
      source-code/Volo.Abp.BlobStoring.Database.SourceCode/Volo.Abp.BlobStoring.Database.SourceCode.zip
  45. BIN
      source-code/Volo.Abp.FeatureManagement.SourceCode/Volo.Abp.FeatureManagement.SourceCode.zip
  46. BIN
      source-code/Volo.Abp.Identity.SourceCode/Volo.Abp.Identity.SourceCode.zip
  47. BIN
      source-code/Volo.Abp.IdentityServer.SourceCode/Volo.Abp.IdentityServer.SourceCode.zip
  48. BIN
      source-code/Volo.Abp.OpenIddict.SourceCode/Volo.Abp.OpenIddict.SourceCode.zip
  49. BIN
      source-code/Volo.Abp.PermissionManagement.SourceCode/Volo.Abp.PermissionManagement.SourceCode.zip
  50. BIN
      source-code/Volo.Abp.SettingManagement.SourceCode/Volo.Abp.SettingManagement.SourceCode.zip
  51. BIN
      source-code/Volo.Abp.TenantManagement.SourceCode/Volo.Abp.TenantManagement.SourceCode.zip
  52. BIN
      source-code/Volo.Abp.Users.SourceCode/Volo.Abp.Users.SourceCode.zip
  53. BIN
      source-code/Volo.Abp.VirtualFileExplorer.SourceCode/Volo.Abp.VirtualFileExplorer.SourceCode.zip
  54. BIN
      source-code/Volo.Blogging.SourceCode/Volo.Blogging.SourceCode.zip
  55. BIN
      source-code/Volo.ClientSimulation.SourceCode/Volo.ClientSimulation.SourceCode.zip
  56. BIN
      source-code/Volo.CmsKit.SourceCode/Volo.CmsKit.SourceCode.zip
  57. BIN
      source-code/Volo.Docs.SourceCode/Volo.Docs.SourceCode.zip
  58. 5
      templates/app/aspnet-core/src/MyCompanyName.MyProjectName.AuthServer/Pages/Index.cshtml
  59. 13
      templates/app/aspnet-core/test/MyCompanyName.MyProjectName.EntityFrameworkCore.Tests/EntityFrameworkCore/MyProjectNameEntityFrameworkCoreTestModule.cs

2
abp_io/AbpIoLocalization/AbpIoLocalization/Commercial/Localization/Resources/en.json

@ -208,7 +208,7 @@
"WhenShouldIRenewMyLicense": "When should I renew my license?",
"WhenShouldIRenewMyLicenseExplanation": "If you renew your license within <strong>1 month</strong> after your license expires, the following discounts will be applied: Team License {0}; Business License {1}; Enterprise License {2}. However, if you renew your license after <strong>1 month</strong> since the expiry date of your license, the renewal price will be the same as the license purchase price and there will be no discount on your renewal.",
"TrialPlan": "Do you have a trial plan?",
"TrialPlanExplanation": "It has a 14 days trial period for the ABP Commercial team license. For more information visit <a href={0} target='_blank'>here</a>. Furthermore, for the Team licenses we provide a 30 days money-back guarantee. You can just request a refund in the first 30 days. For the Business and Enterprise licenses, we provide 60% refund in 30 days. This is because Business and Enterprise licenses include the full source code of all the modules and the themes.",
"TrialPlanExplanation": "No, there is no trial version for ABP Commercial. You can check the community edition to understand the code quality and approaches. We also offer a 30-day money-back guarantee for the Team license, no questions asked! You can request a refund within the first 30 days. We provide a 60% refund within 30 days for Business and Enterprise licenses. This is because the Business and Enterprise licenses contain the full source-code of all the modules and themes.",
"DoYouAcceptBankWireTransfer": "Do you accept bank wire transfers?",
"DoYouAcceptBankWireTransferExplanation": "Yes, we accept bank wire transfers.<br/>After sending the license fee via bank transfer, send your receipt and requested license type to accounting@volosoft.com.<br/>Our international bank account information:",
"HowToUpgrade": "How to upgrade existing applications when a new version is available?",

59
docs/en/Modules/OpenIddict.md

@ -414,6 +414,65 @@ https://documentation.openiddict.com/guides/index.html#events-model
https://documentation.openiddict.com/configuration/proof-key-for-code-exchange.html
### Setting Tokens Lifetime
Update `PreConfigureServices` method of AuthServerModule (or HttpApiHostModule if you don't have tiered/separate-authserver) file:
```csharp
PreConfigure<OpenIddictServerBuilder>(builder =>
{
builder.SetAuthorizationCodeLifetime(TimeSpan.FromMinutes(30));
builder.SetAccessTokenLifetime(TimeSpan.FromMinutes(30));
builder.SetIdentityTokenLifetime(TimeSpan.FromMinutes(30));
builder.SetRefreshTokenLifetime(TimeSpan.FromDays(14));
});
```
### Refresh Token
To use refresh token, it must be supported by OpenIddictServer and the `refresh_token` must be requested by the application.
> **Note:** Angular application is already configured to use `refresh_token`.
#### Configuring OpenIddictServer
Update the **OpenIddictDataSeedContributor**, add `OpenIddictConstants.GrantTypes.RefreshToken` to grant types in `CreateApplicationAsync` method:
```csharp
await CreateApplicationAsync(
...
grantTypes: new List<string> //Hybrid flow
{
OpenIddictConstants.GrantTypes.AuthorizationCode,
OpenIddictConstants.GrantTypes.Implicit,
OpenIddictConstants.GrantTypes.RefreshToken,
},
...
```
> **Note:** You need to re-create this client if you have generated the database already.
#### Configuring Application:
You need to request the **offline_access scope** to be able to receive `refresh_token`.
In **Razor/MVC, Blazor-Server applications**, add `options.Scope.Add("offline_access");` to **OpenIdConnect** options. These application templates are using cookie authentication by default and has default cookie expire options set as:
```csharp
.AddCookie("Cookies", options =>
{
options.ExpireTimeSpan = TimeSpan.FromDays(365);
})
```
[Cookie ExpireTimeSpan will ignore access_token expiration](https://learn.microsoft.com/en-us/dotnet/api/Microsoft.AspNetCore.Authentication.Cookies.CookieAuthenticationOptions.ExpireTimeSpan?view=aspnetcore-7.0&viewFallbackFrom=net-7.0) and expired access_token will still be valid if it is set to higher value than the `refresh_token lifetime`. It is recommended to keep **Cookie ExpireTimeSpan** and the **Refresh Token lifetime** same, hence the new token will be persisted in the cookie.
In **Blazor wasm** applications, add `options.ProviderOptions.DefaultScopes.Add("offline_access");` to **AddOidcAuthentication** options.
In **Angular** applications, add `offline_access` to **oAuthConfig** scopes in *environment.ts* file. (Angular applications already have this configuration).
## Demo projects
In the module's `app` directory there are six projects(including `angular`)

36
docs/en/Multi-Tenancy.md

@ -32,25 +32,6 @@ Configure<AbpMultiTenancyOptions>(options =>
> Multi-Tenancy is disabled in the ABP Framework by default. However, it is **enabled by default** when you create a new solution using the [startup template](Startup-Templates/Application.md). `MultiTenancyConsts` class in the solution has a constant to control it in a single place.
### AbpMultiTenancyOptions: Handle inactive and non-existent tenants.
The `MultiTenancyMiddlewareErrorPageBuilder` of `AbpMultiTenancyOptions` is used to handle inactive and non-existent tenants.
It will respond to an error page by default, you can change it if you want, eg: only output the error log and continue ASP NET Core's request pipeline.
```csharp
Configure<AbpMultiTenancyOptions>(options =>
{
options.MultiTenancyMiddlewareErrorPageBuilder = async (context, exception) =>
{
// Handle the exception.
// Return true to stop the pipeline, false to continue.
return true;
};
});
```
### Database Architecture
ABP Framework supports all the following approaches to store the tenant data in the database;
@ -273,6 +254,23 @@ class SomeComponent {
> However, we don't suggest to change this value since some clients may assume the the `__tenant` as the parameter name and they might need to manually configure then.
The `MultiTenancyMiddlewareErrorPageBuilder` is used to handle inactive and non-existent tenants.
It will respond to an error page by default, you can change it if you want, eg: only output the error log and continue ASP NET Core's request pipeline.
```csharp
Configure<AbpAspNetCoreMultiTenancyOptions>(options =>
{
options.MultiTenancyMiddlewareErrorPageBuilder = async (context, exception) =>
{
// Handle the exception.
// Return true to stop the pipeline, false to continue.
return true;
};
});
```
##### Domain/Subdomain Tenant Resolver
In a real application, most of times you will want to determine current tenant either by subdomain (like mytenant1.mydomain.com) or by the whole domain (like mytenant.com). If so, you can configure the `AbpTenantResolveOptions` to add the domain tenant resolver.

1
docs/en/UI/Angular/List-Service.md

@ -201,3 +201,4 @@ You may use observables in combination with [AsyncPipe](https://angular.io/guide
<input type="text" name="search" [(ngModel)]="list.filter">
```
ABP doesn't have a built-in filtering mechanism. You need to implement it yourself and handle the `filter` property in the backend.

5
framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Auth/LoginInfo.cs

@ -1,8 +1,11 @@

using System;
namespace Volo.Abp.Cli.Auth;
public class LoginInfo
{
public Guid? Id { get; set; }
public string Name { get; set; }
public string Surname { get; set; }

5
framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/LoginInfoCommand.cs

@ -1,5 +1,4 @@
using System;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
using System.Text;
using System.Threading.Tasks;
@ -41,7 +40,7 @@ public class LoginInfoCommand : IConsoleCommand, ITransientDependency
var sb = new StringBuilder();
sb.AppendLine("");
sb.AppendLine($"Login info:");
sb.AppendLine("Login info:");
sb.AppendLine($"Name: {loginInfo.Name}");
sb.AppendLine($"Surname: {loginInfo.Surname}");
sb.AppendLine($"Username: {loginInfo.Username}");

4
framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/Steps/ChangeThemeStep.cs

@ -308,7 +308,7 @@ public class ChangeThemeStep : ProjectBuildPipelineStep
{
var projectNames = new[]
{
".Web", ".HttpApi.Host", ".AuthServer",
".Web", ".HttpApi.Host", ".AuthServer", ".Web.Public", ".Web.Public.Host",
"" //for app-nolayers-mvc
};
@ -781,6 +781,8 @@ public class ChangeThemeStep : ProjectBuildPipelineStep
{".Web", "MyProjectNameWebModule"},
{".HttpApi.Host", "MyProjectNameHttpApiHostModule"},
{".AuthServer", "MyProjectNameAuthServerModule"},
{".Web.Public", "MyProjectNameWebPublicModule"},
{".Web.Public.Host", "MyProjectNameWebPublicModule"},
{"", "MyProjectNameWebModule"}
};

2
framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Templates/App/AppTemplateBase.cs

@ -258,6 +258,8 @@ public abstract class AppTemplateBase : TemplateInfo
{
"/MyCompanyName.MyProjectName.Web/package.json",
"/MyCompanyName.MyProjectName.Web.Host/package.json",
"/MyCompanyName.MyProjectName.Web.Public/package.json",
"/MyCompanyName.MyProjectName.Web.Public.Host/package.json",
"/MyCompanyName.MyProjectName.HttpApi.HostWithIds/package.json",
"/MyCompanyName.MyProjectName.HttpApi.Host/package.json",
"/MyCompanyName.MyProjectName.AuthServer/package.json",

41
framework/src/Volo.Abp.MultiLingualObjects/Volo.Abp.MultiLingualObjects.csproj

@ -1,20 +1,21 @@
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="..\..\..\configureawait.props" />
<Import Project="..\..\..\common.props" />
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<PackageId>Volo.Abp.MultiLingualObject</PackageId>
<AssetTargetFallback>$(AssetTargetFallback);portable-net45+win8+wp8+wpa81;</AssetTargetFallback>
<GenerateAssemblyConfigurationAttribute>false</GenerateAssemblyConfigurationAttribute>
<GenerateAssemblyCompanyAttribute>false</GenerateAssemblyCompanyAttribute>
<GenerateAssemblyProductAttribute>false</GenerateAssemblyProductAttribute>
<RootNamespace />
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\Volo.Abp.Localization\Volo.Abp.Localization.csproj" />
</ItemGroup>
</Project>
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="..\..\..\configureawait.props" />
<Import Project="..\..\..\common.props" />
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<PackageId>Volo.Abp.MultiLingualObject</PackageId>
<AssetTargetFallback>$(AssetTargetFallback);portable-net45+win8+wp8+wpa81;</AssetTargetFallback>
<GenerateAssemblyConfigurationAttribute>false</GenerateAssemblyConfigurationAttribute>
<GenerateAssemblyCompanyAttribute>false</GenerateAssemblyCompanyAttribute>
<GenerateAssemblyProductAttribute>false</GenerateAssemblyProductAttribute>
<RootNamespace />
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\Volo.Abp.Localization\Volo.Abp.Localization.csproj" />
</ItemGroup>
</Project>

24
framework/src/Volo.Abp.MultiLingualObjects/Volo/Abp/MultiLingualObjects/IMultiLingualObjectManager.cs

@ -5,16 +5,30 @@ namespace Volo.Abp.MultiLingualObjects;
public interface IMultiLingualObjectManager
{
Task<TTranslation> GetTranslationAsync<TMultiLingual, TTranslation>(
Task<TTranslation?> GetTranslationAsync<TMultiLingual, TTranslation>(
TMultiLingual multiLingual,
string culture = null,
string? culture = null,
bool fallbackToParentCultures = true)
where TMultiLingual : IMultiLingualObject<TTranslation>
where TTranslation : class, IObjectTranslation;
Task<TTranslation> GetTranslationAsync<TTranslation>(
ICollection<TTranslation> translations,
string culture = null,
Task<TTranslation?> GetTranslationAsync<TTranslation>(
IEnumerable<TTranslation> translations,
string? culture = null,
bool fallbackToParentCultures = true)
where TTranslation : class, IObjectTranslation;
Task<List<TTranslation?>> GetBulkTranslationsAsync<TTranslation>(
IEnumerable<IEnumerable<TTranslation>> translationsCombined,
string? culture = null,
bool fallbackToParentCultures = true)
where TTranslation : class, IObjectTranslation;
Task<List<(TMultiLingual entity, TTranslation? translation)>> GetBulkTranslationsAsync<TMultiLingual, TTranslation>(
IEnumerable<TMultiLingual> multiLinguals,
string? culture = null,
bool fallbackToParentCultures = true)
where TMultiLingual : IMultiLingualObject<TTranslation>
where TTranslation : class, IObjectTranslation;
}

123
framework/src/Volo.Abp.MultiLingualObjects/Volo/Abp/MultiLingualObjects/MultiLingualObjectManager.cs

@ -19,16 +19,16 @@ public class MultiLingualObjectManager : IMultiLingualObjectManager, ITransientD
{
SettingProvider = settingProvider;
}
public virtual async Task<TTranslation> GetTranslationAsync<TTranslation>(
ICollection<TTranslation> translations,
string culture,
public virtual async Task<TTranslation?> GetTranslationAsync<TTranslation>(
IEnumerable<TTranslation> translations,
string? culture,
bool fallbackToParentCultures)
where TTranslation : class, IObjectTranslation
{
culture ??= CultureInfo.CurrentUICulture.Name;
if (translations.IsNullOrEmpty())
if (translations == null || !translations.Any())
{
return null;
}
@ -65,9 +65,9 @@ public class MultiLingualObjectManager : IMultiLingualObjectManager, ITransientD
return translation;
}
public virtual Task<TTranslation> GetTranslationAsync<TMultiLingual, TTranslation>(
public virtual Task<TTranslation?> GetTranslationAsync<TMultiLingual, TTranslation>(
TMultiLingual multiLingual,
string culture = null,
string? culture = null,
bool fallbackToParentCultures = true)
where TMultiLingual : IMultiLingualObject<TTranslation>
where TTranslation : class, IObjectTranslation
@ -75,13 +75,13 @@ public class MultiLingualObjectManager : IMultiLingualObjectManager, ITransientD
return GetTranslationAsync(multiLingual.Translations, culture: culture, fallbackToParentCultures: fallbackToParentCultures);
}
protected virtual TTranslation GetTranslationBasedOnCulturalRecursive<TTranslation>(
CultureInfo culture, ICollection<TTranslation> translations, int currentDepth)
protected virtual TTranslation? GetTranslationBasedOnCulturalRecursive<TTranslation>(
CultureInfo culture, IEnumerable<TTranslation> translations, int currentDepth)
where TTranslation : class, IObjectTranslation
{
if (culture == null ||
culture.Name.IsNullOrWhiteSpace() ||
translations.IsNullOrEmpty() ||
translations == null || !translations.Any() ||
currentDepth > MaxCultureFallbackDepth)
{
return null;
@ -89,5 +89,108 @@ public class MultiLingualObjectManager : IMultiLingualObjectManager, ITransientD
var translation = translations.FirstOrDefault(pt => pt.Language.Equals(culture.Name, StringComparison.OrdinalIgnoreCase));
return translation ?? GetTranslationBasedOnCulturalRecursive(culture.Parent, translations, currentDepth + 1);
}
}
public virtual async Task<List<TTranslation?>> GetBulkTranslationsAsync<TTranslation>(IEnumerable<IEnumerable<TTranslation>> translationsCombined, string? culture, bool fallbackToParentCultures)
where TTranslation : class, IObjectTranslation
{
culture ??= CultureInfo.CurrentUICulture.Name;
if (translationsCombined == null || !translationsCombined.Any())
{
return new();
}
var someHaveNoTranslations = false;
var res = new List<TTranslation?>();
foreach (var translations in translationsCombined)
{
if (!translations.Any())
{
//if the src has no translations, don't try to find a translation
res.Add(null);
continue;
}
var translation = translations.FirstOrDefault(pt => pt.Language == culture);
if (translation != null)
{
res.Add(translation);
}
else
{
if (fallbackToParentCultures)
{
translation = GetTranslationBasedOnCulturalRecursive(
CultureInfo.CurrentUICulture.Parent,
translations,
0
);
if (translation != null)
{
res.Add(translation);
}
else
{
res.Add(null);
someHaveNoTranslations = true;
}
}
else
{
res.Add(null);
someHaveNoTranslations = true;
}
}
}
if (someHaveNoTranslations)
{
var defaultLanguage = await SettingProvider.GetOrNullAsync(LocalizationSettingNames.DefaultLanguage);
var index = 0;
foreach (var translations in translationsCombined)
{
if (!translations.Any())
{
//don't try to find a translation
}
else
{
var translation = res[index];
if (translation != null)
{
continue;
}
translation = translations.FirstOrDefault(pt => pt.Language == defaultLanguage);
if (translation != null)
{
res[index] = translation;
}
else
{
res[index] = translations.FirstOrDefault();
}
}
index++;
}
}
return res;
}
public virtual async Task<List<(TMultiLingual entity, TTranslation? translation)>> GetBulkTranslationsAsync<TMultiLingual, TTranslation>(IEnumerable<TMultiLingual> multiLinguals, string? culture, bool fallbackToParentCultures)
where TMultiLingual : IMultiLingualObject<TTranslation>
where TTranslation : class, IObjectTranslation
{
var resInitial = await GetBulkTranslationsAsync(multiLinguals.Select(x => x.Translations), culture, fallbackToParentCultures);
var index = 0;
var res = new List<(TMultiLingual entity, TTranslation? translation)>();
foreach (var item in multiLinguals)
{
var t = resInitial[index++];
res.Add((item, t));
}
return res;
}
}

19
framework/src/Volo.Abp.UI.Navigation/Volo/Abp/Ui/Navigation/ApplicationMenu.cs

@ -1,3 +1,4 @@
using System;
using System.Collections.Generic;
using JetBrains.Annotations;
using Volo.Abp.Data;
@ -5,7 +6,7 @@ using Volo.Abp.UI.Navigation;
namespace Volo.Abp.UI.Navigation;
public class ApplicationMenu : IHasMenuItems
public class ApplicationMenu : IHasMenuItems, IHasMenuGroups
{
/// <summary>
/// Unique name of the menu in the application.
@ -31,6 +32,10 @@ public class ApplicationMenu : IHasMenuItems
[NotNull]
public ApplicationMenuItemList Items { get; }
/// <inheritdoc cref="IHasMenuGroups.Groups"/>
[NotNull]
public ApplicationMenuGroupList Groups { get; }
/// <summary>
/// Can be used to store a custom object related to this menu.
/// </summary>
@ -47,6 +52,7 @@ public class ApplicationMenu : IHasMenuItems
DisplayName = displayName ?? Name;
Items = new ApplicationMenuItemList();
Groups = new ApplicationMenuGroupList();
}
/// <summary>
@ -60,6 +66,17 @@ public class ApplicationMenu : IHasMenuItems
return this;
}
/// <summary>
/// Adds a <see cref="ApplicationMenuGroup"/> to <see cref="Groups"/>.
/// </summary>
/// <param name="group"><see cref="ApplicationMenuGroup"/> to be added</param>
/// <returns>This <see cref="ApplicationMenu"/> object</returns>
public ApplicationMenu AddGroup([NotNull] ApplicationMenuGroup group)
{
Groups.Add(group);
return this;
}
/// <summary>
/// Adds a custom data item to <see cref="CustomData"/> with given key &amp; value.
/// </summary>

50
framework/src/Volo.Abp.UI.Navigation/Volo/Abp/Ui/Navigation/ApplicationMenuExtensions.cs

@ -64,4 +64,54 @@ public static class ApplicationMenuExtensions
return menuWithItems;
}
[NotNull]
public static ApplicationMenuGroup GetMenuGroup(
[NotNull] this IHasMenuGroups menuWithGroups,
string groupName)
{
var menuGroup = menuWithGroups.GetMenuGroupOrNull(groupName);
if (menuGroup == null)
{
throw new AbpException($"Could not find a group item with given name: {groupName}");
}
return menuGroup;
}
[CanBeNull]
public static ApplicationMenuGroup GetMenuGroupOrNull(
[NotNull] this IHasMenuGroups menuWithGroups,
string menuGroupName)
{
Check.NotNull(menuWithGroups, nameof(menuWithGroups));
return menuWithGroups.Groups.FirstOrDefault(group => group.Name == menuGroupName);
}
public static bool TryRemoveMenuGroup(
[NotNull] this IHasMenuGroups menuWithGroups,
string menuGroupName)
{
Check.NotNull(menuWithGroups, nameof(menuWithGroups));
return menuWithGroups.Groups.RemoveAll(group => group.Name == menuGroupName) > 0;
}
[NotNull]
public static IHasMenuGroups SetMenuGroupOrder(
[NotNull] this IHasMenuGroups menuWithGroups,
string menuGroupName,
int order)
{
Check.NotNull(menuWithGroups, nameof(menuWithGroups));
var menuGroup = menuWithGroups.GetMenuGroupOrNull(menuGroupName);
if (menuGroup != null)
{
menuGroup.Order = order;
}
return menuWithGroups;
}
}

67
framework/src/Volo.Abp.UI.Navigation/Volo/Abp/Ui/Navigation/ApplicationMenuGroup.cs

@ -0,0 +1,67 @@
using JetBrains.Annotations;
namespace Volo.Abp.UI.Navigation;
public class ApplicationMenuGroup
{
private string _displayName;
/// <summary>
/// Default <see cref="Order"/> value of a group item.
/// </summary>
public const int DefaultOrder = 1000;
/// <summary>
/// Unique name of the group in the application.
/// </summary>
[NotNull]
public string Name { get; }
/// <summary>
/// Display name of the group.
/// </summary>
[NotNull]
public string DisplayName {
get { return _displayName; }
set {
Check.NotNullOrWhiteSpace(value, nameof(value));
_displayName = value;
}
}
/// <summary>
/// Can be used to render the element with a specific Id for DOM selections.
/// </summary>
public string ElementId { get; set; }
/// <summary>
/// The Display order of the group.
/// Default value: 1000.
/// </summary>
public int Order { get; set; }
public ApplicationMenuGroup(
[NotNull] string name,
[NotNull] string displayName,
string elementId = null,
int order = DefaultOrder)
{
Check.NotNullOrWhiteSpace(name, nameof(name));
Check.NotNullOrWhiteSpace(displayName, nameof(displayName));
Name = name;
DisplayName = displayName;
ElementId = elementId;
Order = order;
}
private string GetDefaultElementId()
{
return "MenuGroup_" + Name;
}
public override string ToString()
{
return $"[ApplicationMenuGroup] Name = {Name}";
}
}

36
framework/src/Volo.Abp.UI.Navigation/Volo/Abp/Ui/Navigation/ApplicationMenuGroupList.cs

@ -0,0 +1,36 @@
using System.Collections.Generic;
using System.Linq;
namespace Volo.Abp.UI.Navigation;
public class ApplicationMenuGroupList: List<ApplicationMenuGroup>
{
public ApplicationMenuGroupList()
{
}
public ApplicationMenuGroupList(int capacity)
: base(capacity)
{
}
public ApplicationMenuGroupList(IEnumerable<ApplicationMenuGroup> collection)
: base(collection)
{
}
public void Normalize()
{
Order();
}
private void Order()
{
var orderedItems = this.OrderBy(item => item.Order).ToArray();
Clear();
AddRange(orderedItems);
}
}

7
framework/src/Volo.Abp.UI.Navigation/Volo/Abp/Ui/Navigation/ApplicationMenuItem.cs

@ -92,6 +92,11 @@ public class ApplicationMenuItem : IHasMenuItems, IHasSimpleStateCheckers<Applic
/// </summary>
public string CssClass { get; set; }
/// <summary>
/// Can be used to group menu items.
/// </summary>
public string GroupName { get; set; }
public ApplicationMenuItem(
[NotNull] string name,
[NotNull] string displayName,
@ -101,6 +106,7 @@ public class ApplicationMenuItem : IHasMenuItems, IHasSimpleStateCheckers<Applic
string target = null,
string elementId = null,
string cssClass = null,
string groupName = null,
string requiredPermissionName = null)
{
Check.NotNullOrWhiteSpace(name, nameof(name));
@ -114,6 +120,7 @@ public class ApplicationMenuItem : IHasMenuItems, IHasSimpleStateCheckers<Applic
Target = target;
ElementId = elementId ?? GetDefaultElementId();
CssClass = cssClass;
GroupName = groupName;
RequiredPermissionName = requiredPermissionName;
StateCheckers = new List<ISimpleStateChecker<ApplicationMenuItem>>();
Items = new ApplicationMenuItemList();

9
framework/src/Volo.Abp.UI.Navigation/Volo/Abp/Ui/Navigation/IHasMenuGroups.cs

@ -0,0 +1,9 @@
namespace Volo.Abp.UI.Navigation;
public interface IHasMenuGroups
{
/// <summary>
/// Menu groups.
/// </summary>
ApplicationMenuGroupList Groups { get; }
}

20
framework/src/Volo.Abp.UI.Navigation/Volo/Abp/Ui/Navigation/MenuManager.cs

@ -96,6 +96,7 @@ public class MenuManager : IMenuManager, ITransientDependency
}
NormalizeMenu(menu);
NormalizeMenuGroup(menu);
return menu;
}
@ -159,4 +160,23 @@ public class MenuManager : IMenuManager, ITransientDependency
menuWithItems.Items.Normalize();
}
protected virtual void NormalizeMenuGroup(ApplicationMenu applicationMenu)
{
foreach (var menuGroup in applicationMenu.Items.Where(x => !x.GroupName.IsNullOrWhiteSpace()).GroupBy(x => x.GroupName))
{
var group = applicationMenu.GetMenuGroupOrNull(menuGroup.First().GroupName);
if (group != null)
{
continue;
}
foreach (var menuItem in menuGroup)
{
menuItem.GroupName = null;
}
}
applicationMenu.Groups.Normalize();
}
}

27
framework/test/Volo.Abp.MultiLingualObjects.Tests/Volo.Abp.MultiLingualObjects.Tests.csproj

@ -1,18 +1,21 @@
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="..\..\..\common.test.props" />
<Import Project="..\..\..\common.test.props" />
<PropertyGroup>
<TargetFramework>net7.0</TargetFramework>
<RootNamespace />
</PropertyGroup>
<PropertyGroup>
<TargetFramework>net7.0</TargetFramework>
<RootNamespace />
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\Volo.Abp.Autofac\Volo.Abp.Autofac.csproj" />
<ProjectReference Include="..\..\src\Volo.Abp.MultiLingualObjects\Volo.Abp.MultiLingualObjects.csproj" />
<ProjectReference Include="..\..\src\Volo.Abp.ObjectMapping\Volo.Abp.ObjectMapping.csproj" />
<ProjectReference Include="..\AbpTestBase\AbpTestBase.csproj" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="$(MicrosoftNETTestSdkPackageVersion)" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\Volo.Abp.Autofac\Volo.Abp.Autofac.csproj" />
<ProjectReference Include="..\..\src\Volo.Abp.MultiLingualObjects\Volo.Abp.MultiLingualObjects.csproj" />
<ProjectReference Include="..\..\src\Volo.Abp.ObjectMapping\Volo.Abp.ObjectMapping.csproj" />
<ProjectReference Include="..\..\src\Volo.Abp.AutoMapper\Volo.Abp.AutoMapper.csproj" />
<ProjectReference Include="..\AbpTestBase\AbpTestBase.csproj" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="$(MicrosoftNETTestSdkPackageVersion)" />
</ItemGroup>
</Project>

53
framework/test/Volo.Abp.MultiLingualObjects.Tests/Volo/Abp/MultiLingualObjects/AbpMultiLingualObjectsTestModule.cs

@ -1,19 +1,34 @@
using Volo.Abp.Autofac;
using Volo.Abp.Localization;
using Volo.Abp.Modularity;
using Volo.Abp.ObjectMapping;
using Volo.Abp.Settings;
namespace Volo.Abp.MultiLingualObjects;
[DependsOn(
typeof(AbpAutofacModule),
typeof(AbpLocalizationModule),
typeof(AbpSettingsModule),
typeof(AbpObjectMappingModule),
typeof(AbpMultiLingualObjectsModule),
typeof(AbpTestBaseModule)
)]
public class AbpMultiLingualObjectsTestModule : AbpModule
{
}
using Microsoft.Extensions.DependencyInjection;
using Volo.Abp.Autofac;
using Volo.Abp.AutoMapper;
using Volo.Abp.Localization;
using Volo.Abp.Modularity;
using Volo.Abp.ObjectMapping;
using Volo.Abp.Settings;
namespace Volo.Abp.MultiLingualObjects;
[DependsOn(
typeof(AbpAutofacModule),
typeof(AbpLocalizationModule),
typeof(AbpSettingsModule),
typeof(AbpObjectMappingModule),
typeof(AbpMultiLingualObjectsModule),
typeof(AbpTestBaseModule),
typeof(AbpAutoMapperModule)
)]
public class AbpMultiLingualObjectsTestModule : AbpModule
{
public override void ConfigureServices(ServiceConfigurationContext context)
{
Configure<AbpSettingOptions>(options =>
{
options.DefinitionProviders.Add<LocalizationSettingProvider>();
});
context.Services.AddAutoMapperObjectMapper<AbpMultiLingualObjectsTestModule>();
Configure<AbpAutoMapperOptions>(options =>
{
options.AddMaps<AbpMultiLingualObjectsTestModule>(validate: true);
});
}
}

135
framework/test/Volo.Abp.MultiLingualObjects.Tests/Volo/Abp/MultiLingualObjects/MultiLingualObjectManager_Tests.cs

@ -1,8 +1,10 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
using Shouldly;
using Volo.Abp.AutoMapper;
using Volo.Abp.Localization;
using Volo.Abp.MultiLingualObjects.TestObjects;
using Volo.Abp.Testing;
@ -14,40 +16,63 @@ public class MultiLingualObjectManager_Tests : AbpIntegratedTest<AbpMultiLingual
{
private readonly IMultiLingualObjectManager _multiLingualObjectManager;
private readonly MultiLingualBook _book;
private readonly List<MultiLingualBook> _books;
private readonly IMapperAccessor _mapperAccessor;
private readonly Dictionary<string, string> _testTranslations = new()
{
["ar"] = "C# التعمق في",
["zh-Hans"] = "深入理解C#",
["en"] = "C# in Depth"
};
public MultiLingualObjectManager_Tests()
{
_multiLingualObjectManager = ServiceProvider.GetRequiredService<IMultiLingualObjectManager>();
_multiLingualObjectManager = ServiceProvider.GetRequiredService<IMultiLingualObjectManager>();
//Single Lookup
_book = GetTestBook("en", "zh-Hans");
//Bulk lookup
_books = new List<MultiLingualBook>
{
//has no translations
GetTestBook(),
//english only
GetTestBook("en"),
//arabic only
GetTestBook("ar"),
//arabic + english
GetTestBook("en","ar"),
//arabic + english + chineese
GetTestBook("en", "ar", "zh-Hans")
};
_mapperAccessor = ServiceProvider.GetRequiredService<IMapperAccessor>();
}
MultiLingualBook GetTestBook(params string[] included)
{
var id = Guid.NewGuid();
_book = new MultiLingualBook(id, 100)
{
Translations = new List<MultiLingualBookTranslation>()
};
var en = new MultiLingualBookTranslation
{
Language = "en",
Name = "C# in Depth",
};
var zh = new MultiLingualBookTranslation
{
Language = "zh-Hans",
Name = "深入理解C#",
};
_book.Translations.Add(en);
_book.Translations.Add(zh);
}
//Single book
var res = new MultiLingualBook(id, 100);
foreach (var language in included)
{
res.Translations.Add(new MultiLingualBookTranslation
{
Language = language,
Name = _testTranslations[language],
});
}
return res;
}
[Fact]
public async Task GetTranslationAsync()
{
using (CultureHelper.Use("en-us"))
{
var translation = await _multiLingualObjectManager.GetTranslationAsync<MultiLingualBook, MultiLingualBookTranslation>(_book);
translation.Name.ShouldBe("C# in Depth");
translation.ShouldNotBeNull();
translation.Name.ShouldBe(_testTranslations["en"]);
}
}
@ -57,8 +82,8 @@ public class MultiLingualObjectManager_Tests : AbpIntegratedTest<AbpMultiLingual
using (CultureHelper.Use("en-us"))
{
var translation = await _multiLingualObjectManager.GetTranslationAsync(_book.Translations);
translation.Name.ShouldBe("C# in Depth");
translation.ShouldNotBeNull();
translation.Name.ShouldBe(_testTranslations["en"]);
}
}
@ -68,8 +93,64 @@ public class MultiLingualObjectManager_Tests : AbpIntegratedTest<AbpMultiLingual
using (CultureHelper.Use("zh-Hans"))
{
var translation = await _multiLingualObjectManager.GetTranslationAsync<MultiLingualBook, MultiLingualBookTranslation>(_book, culture: "en");
translation.ShouldNotBeNull();
translation.Name.ShouldBe(_testTranslations["en"]);
}
}
[Fact]
public async Task GetBulkTranslationsAsync()
{
using (CultureHelper.Use("en-us"))
{
var translations = await _multiLingualObjectManager.GetBulkTranslationsAsync<MultiLingualBook, MultiLingualBookTranslation>(_books);
foreach (var (entity, translation) in translations)
{
if (entity.Translations.Any(x => x.Language == "en"))
{
translation.ShouldNotBeNull();
translation.Name.ShouldBe(_testTranslations["en"]);
}
else
{
translation.ShouldBeNull();
}
}
}
}
translation.Name.ShouldBe("C# in Depth");
[Fact]
public async Task GetBulkTranslationsFromListAsync()
{
using (CultureHelper.Use("en-us"))
{
var translations = await _multiLingualObjectManager.GetBulkTranslationsAsync(_books.Select(x => x.Translations));
foreach (var translation in translations)
{
translation?.Name.ShouldBe(_testTranslations["en"]);
}
}
}
[Fact]
public async Task TestBulkMapping()
{
using (CultureHelper.Use("en-us"))
{
var translations = await _multiLingualObjectManager.GetBulkTranslationsAsync<MultiLingualBook, MultiLingualBookTranslation>(_books);
var translationsDict = translations.ToDictionary(x => x.entity.Id, x => x.translation);
var mapped = _mapperAccessor.Mapper.Map<List<MultiLingualBook>, List<MultiLingualBookDto>>(_books, options =>
{
options.Items.Add(nameof(MultiLingualBookTranslation), translationsDict);
});
Assert.Equal(mapped.Count, _books.Count);
for (int i = 0; i < mapped.Count; i++)
{
var og = _books[i];
var m = mapped[i];
Assert.Equal(og.Translations.FirstOrDefault(x => x.Language == "en")?.Name, m.Name);
}
}
}
}

23
framework/test/Volo.Abp.MultiLingualObjects.Tests/Volo/Abp/MultiLingualObjects/MultiLingualObjectTestProfile.cs

@ -0,0 +1,23 @@
namespace Volo.Abp.MultiLingualObjects;
using System;
using System.Collections.Generic;
using global::AutoMapper;
using Volo.Abp.MultiLingualObjects.TestObjects;
public class MultiLingualObjectTestProfile : Profile
{
public MultiLingualObjectTestProfile()
{
CreateMap<MultiLingualBook, MultiLingualBookDto>()
.ForMember(x => x.Name,
x => x.MapFrom((src, target, member, context) =>
{
if (context.Items.TryGetValue(nameof(MultiLingualBookTranslation), out var translationsRaw) && translationsRaw is IReadOnlyDictionary<Guid, MultiLingualBookTranslation> translations)
{
return translations.GetValueOrDefault(src.Id)?.Name;
}
return null;
}));
}
}

38
framework/test/Volo.Abp.MultiLingualObjects.Tests/Volo/Abp/MultiLingualObjects/TestObjects/MultiLingualBook.cs

@ -1,19 +1,19 @@
using System;
using System.Collections.Generic;
namespace Volo.Abp.MultiLingualObjects.TestObjects;
public class MultiLingualBook : IMultiLingualObject<MultiLingualBookTranslation>
{
public MultiLingualBook(Guid id, decimal price)
{
Id = id;
Price = price;
}
public Guid Id { get; }
public decimal Price { get; set; }
public ICollection<MultiLingualBookTranslation> Translations { get; set; }
}
using System;
using System.Collections.Generic;
namespace Volo.Abp.MultiLingualObjects.TestObjects;
public class MultiLingualBook : IMultiLingualObject<MultiLingualBookTranslation>
{
public MultiLingualBook(Guid id, decimal price)
{
Id = id;
Price = price;
}
public Guid Id { get; }
public decimal Price { get; set; }
public ICollection<MultiLingualBookTranslation> Translations { get; set; } = new List<MultiLingualBookTranslation>();
}

24
framework/test/Volo.Abp.MultiLingualObjects.Tests/Volo/Abp/MultiLingualObjects/TestObjects/MultiLingualBookDto.cs

@ -1,12 +1,12 @@
using System;
namespace Volo.Abp.MultiLingualObjects.TestObjects;
public class MultiLingualBookDto
{
public Guid Id { get; set; }
public string Name { get; set; }
public decimal Price { get; set; }
}
using System;
namespace Volo.Abp.MultiLingualObjects.TestObjects;
public class MultiLingualBookDto
{
public Guid Id { get; set; }
public string? Name { get; set; }
public decimal Price { get; set; }
}

16
framework/test/Volo.Abp.MultiLingualObjects.Tests/Volo/Abp/MultiLingualObjects/TestObjects/MultiLingualBookTranslation.cs

@ -1,8 +1,8 @@
namespace Volo.Abp.MultiLingualObjects.TestObjects;
public class MultiLingualBookTranslation : IObjectTranslation
{
public string Name { get; set; }
public string Language { get; set; }
}
namespace Volo.Abp.MultiLingualObjects.TestObjects;
public class MultiLingualBookTranslation : IObjectTranslation
{
public string? Name { get; set; }
public required string Language { get; set; }
}

1
framework/test/Volo.Abp.UI.Navigation.Tests/Volo/Abp/Ui/Navigation/AbpUiNavigationTestModule.cs

@ -16,6 +16,7 @@ public class AbpUiNavigationTestModule : AbpModule
options.MenuContributors.Add(new MenuManager_Tests.TestMenuContributor1());
options.MenuContributors.Add(new MenuManager_Tests.TestMenuContributor2());
options.MenuContributors.Add(new MenuManager_Tests.TestMenuContributor3());
options.MenuContributors.Add(new MenuManager_Tests.TestMenuContributor4());
options.MainMenuNames.Add(MenuManager_Tests.TestMenuContributor3.MenuName);
});

46
framework/test/Volo.Abp.UI.Navigation.Tests/Volo/Abp/Ui/Navigation/MenuManager_Tests.cs

@ -46,7 +46,7 @@ public class MenuManager_Tests : AbpIntegratedTest<AbpUiNavigationTestModule>
mainMenu.Name.ShouldBe(StandardMenus.Main);
mainMenu.DisplayName.ShouldBe("Main Menu");
mainMenu.Items.Count.ShouldBe(2);
mainMenu.Items.Count.ShouldBe(5);
mainMenu.Items[0].Name.ShouldBe("Dashboard");
mainMenu.Items[1].Name.ShouldBe(DefaultMenuNames.Application.Main.Administration);
mainMenu.Items[1].Items[0].Name.ShouldBe("Administration.UserManagement");
@ -63,12 +63,29 @@ public class MenuManager_Tests : AbpIntegratedTest<AbpUiNavigationTestModule>
mainMenu.Name.ShouldBe(StandardMenus.Main);
mainMenu.Items.Count.ShouldBe(3);
mainMenu.Items.Count.ShouldBe(6);
mainMenu.Items.ShouldContain(x => x.Name == "Products");
mainMenu.Items.ShouldContain(x => x.Name == "Dashboard");
}
[Fact]
public async Task GetMainMenuAsync_GroupMenuItems()
{
var mainMenu = await _menuManager.GetMainMenuAsync();
mainMenu.Name.ShouldBe(StandardMenus.Main);
mainMenu.Items.Count.ShouldBe(6);
mainMenu.Items[2].GroupName.ShouldBe("Layouts");
mainMenu.Items[3].GroupName.ShouldBe("Layouts");
mainMenu.Items[4].GroupName.ShouldBe(null); // No group defined
var layoutsGroup = mainMenu.GetMenuGroup("Layouts");
layoutsGroup.Name.ShouldBe("Layouts");
layoutsGroup.DisplayName.ShouldBe("Layouts");
}
/* Adds menu items:
* - Administration
* - User Management
@ -149,4 +166,29 @@ public class MenuManager_Tests : AbpIntegratedTest<AbpUiNavigationTestModule>
return Task.CompletedTask;
}
}
/* Adds group and menu items:
* - Layouts
* - Toolbars
* - Page Header
*/
public class TestMenuContributor4 : IMenuContributor
{
public Task ConfigureMenuAsync(MenuConfigurationContext context)
{
if (context.Menu.Name != StandardMenus.Main)
{
return Task.CompletedTask;
}
context.Menu.AddGroup(new ApplicationMenuGroup("Layouts", "Layouts"));
context.Menu.AddItem(new ApplicationMenuItem("Toolbars", "Toolbars", url: "/layouts/toolbars", groupName: "Layouts"));
context.Menu.AddItem(new ApplicationMenuItem("PageHeader", "Page Header", url: "/layouts/page-header", groupName: "Layouts"));
context.Menu.AddItem(new ApplicationMenuItem("Branding", "Branding", url: "/layouts/branding", groupName: "NotDefinedGroup"));
return Task.CompletedTask;
}
}
}

2
modules/cms-kit/host/Volo.CmsKit.IdentityServer/package.json

@ -3,6 +3,6 @@
"name": "my-app-identityserver",
"private": true,
"dependencies": {
"@abp/aspnetcore.mvc.ui.theme.basic": "^7.1.0-rc.2"
"@abp/aspnetcore.mvc.ui.theme.basic": "^7.1.0-rc.3"
}
}

17
modules/openiddict/app/OpenIddict.Demo.Server/Migrations/20230106050616_Initial.Designer.cs → modules/openiddict/app/OpenIddict.Demo.Server/Migrations/20230307054116_Initial.Designer.cs

@ -13,7 +13,7 @@ using Volo.Abp.EntityFrameworkCore;
namespace OpenIddict.Demo.Server.Migrations
{
[DbContext(typeof(ServerDbContext))]
[Migration("20230106050616_Initial")]
[Migration("20230307054116_Initial")]
partial class Initial
{
/// <inheritdoc />
@ -235,6 +235,9 @@ namespace OpenIddict.Demo.Server.Migrations
.HasColumnType("nvarchar(40)")
.HasColumnName("ConcurrencyStamp");
b.Property<int>("EntityVersion")
.HasColumnType("int");
b.Property<string>("ExtraProperties")
.HasColumnType("nvarchar(max)")
.HasColumnName("ExtraProperties");
@ -421,6 +424,9 @@ namespace OpenIddict.Demo.Server.Migrations
.HasDefaultValue(false)
.HasColumnName("EmailConfirmed");
b.Property<int>("EntityVersion")
.HasColumnType("int");
b.Property<string>("ExtraProperties")
.HasColumnType("nvarchar(max)")
.HasColumnName("ExtraProperties");
@ -497,6 +503,9 @@ namespace OpenIddict.Demo.Server.Migrations
.HasColumnType("nvarchar(256)")
.HasColumnName("SecurityStamp");
b.Property<bool>("ShouldChangePasswordOnNextLogin")
.HasColumnType("bit");
b.Property<string>("Surname")
.HasMaxLength(64)
.HasColumnType("nvarchar(64)")
@ -699,6 +708,9 @@ namespace OpenIddict.Demo.Server.Migrations
.HasColumnType("nvarchar(128)")
.HasColumnName("DisplayName");
b.Property<int>("EntityVersion")
.HasColumnType("int");
b.Property<string>("ExtraProperties")
.HasColumnType("nvarchar(max)")
.HasColumnName("ExtraProperties");
@ -1277,6 +1289,9 @@ namespace OpenIddict.Demo.Server.Migrations
.HasColumnType("datetime2")
.HasColumnName("DeletionTime");
b.Property<int>("EntityVersion")
.HasColumnType("int");
b.Property<string>("ExtraProperties")
.HasColumnType("nvarchar(max)")
.HasColumnName("ExtraProperties");

5
modules/openiddict/app/OpenIddict.Demo.Server/Migrations/20230106050616_Initial.cs → modules/openiddict/app/OpenIddict.Demo.Server/Migrations/20230307054116_Initial.cs

@ -106,6 +106,7 @@ namespace OpenIddict.Demo.Server.Migrations
ParentId = table.Column<Guid>(type: "uniqueidentifier", nullable: true),
Code = table.Column<string>(type: "nvarchar(95)", maxLength: 95, nullable: false),
DisplayName = table.Column<string>(type: "nvarchar(128)", maxLength: 128, nullable: false),
EntityVersion = table.Column<int>(type: "int", nullable: false),
ExtraProperties = table.Column<string>(type: "nvarchar(max)", nullable: true),
ConcurrencyStamp = table.Column<string>(type: "nvarchar(40)", maxLength: 40, nullable: true),
CreationTime = table.Column<DateTime>(type: "datetime2", nullable: false),
@ -186,6 +187,7 @@ namespace OpenIddict.Demo.Server.Migrations
IsDefault = table.Column<bool>(type: "bit", nullable: false),
IsStatic = table.Column<bool>(type: "bit", nullable: false),
IsPublic = table.Column<bool>(type: "bit", nullable: false),
EntityVersion = table.Column<int>(type: "int", nullable: false),
ExtraProperties = table.Column<string>(type: "nvarchar(max)", nullable: true),
ConcurrencyStamp = table.Column<string>(type: "nvarchar(40)", maxLength: 40, nullable: true)
},
@ -240,6 +242,7 @@ namespace OpenIddict.Demo.Server.Migrations
{
Id = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
Name = table.Column<string>(type: "nvarchar(64)", maxLength: 64, nullable: false),
EntityVersion = table.Column<int>(type: "int", nullable: false),
ExtraProperties = table.Column<string>(type: "nvarchar(max)", nullable: true),
ConcurrencyStamp = table.Column<string>(type: "nvarchar(40)", maxLength: 40, nullable: true),
CreationTime = table.Column<DateTime>(type: "datetime2", nullable: false),
@ -278,6 +281,8 @@ namespace OpenIddict.Demo.Server.Migrations
LockoutEnd = table.Column<DateTimeOffset>(type: "datetimeoffset", nullable: true),
LockoutEnabled = table.Column<bool>(type: "bit", nullable: false, defaultValue: false),
AccessFailedCount = table.Column<int>(type: "int", nullable: false, defaultValue: 0),
ShouldChangePasswordOnNextLogin = table.Column<bool>(type: "bit", nullable: false),
EntityVersion = table.Column<int>(type: "int", nullable: false),
ExtraProperties = table.Column<string>(type: "nvarchar(max)", nullable: true),
ConcurrencyStamp = table.Column<string>(type: "nvarchar(40)", maxLength: 40, nullable: true),
CreationTime = table.Column<DateTime>(type: "datetime2", nullable: false),

15
modules/openiddict/app/OpenIddict.Demo.Server/Migrations/ServerDbContextModelSnapshot.cs

@ -232,6 +232,9 @@ namespace OpenIddict.Demo.Server.Migrations
.HasColumnType("nvarchar(40)")
.HasColumnName("ConcurrencyStamp");
b.Property<int>("EntityVersion")
.HasColumnType("int");
b.Property<string>("ExtraProperties")
.HasColumnType("nvarchar(max)")
.HasColumnName("ExtraProperties");
@ -418,6 +421,9 @@ namespace OpenIddict.Demo.Server.Migrations
.HasDefaultValue(false)
.HasColumnName("EmailConfirmed");
b.Property<int>("EntityVersion")
.HasColumnType("int");
b.Property<string>("ExtraProperties")
.HasColumnType("nvarchar(max)")
.HasColumnName("ExtraProperties");
@ -494,6 +500,9 @@ namespace OpenIddict.Demo.Server.Migrations
.HasColumnType("nvarchar(256)")
.HasColumnName("SecurityStamp");
b.Property<bool>("ShouldChangePasswordOnNextLogin")
.HasColumnType("bit");
b.Property<string>("Surname")
.HasMaxLength(64)
.HasColumnType("nvarchar(64)")
@ -696,6 +705,9 @@ namespace OpenIddict.Demo.Server.Migrations
.HasColumnType("nvarchar(128)")
.HasColumnName("DisplayName");
b.Property<int>("EntityVersion")
.HasColumnType("int");
b.Property<string>("ExtraProperties")
.HasColumnType("nvarchar(max)")
.HasColumnName("ExtraProperties");
@ -1274,6 +1286,9 @@ namespace OpenIddict.Demo.Server.Migrations
.HasColumnType("datetime2")
.HasColumnName("DeletionTime");
b.Property<int>("EntityVersion")
.HasColumnType("int");
b.Property<string>("ExtraProperties")
.HasColumnType("nvarchar(max)")
.HasColumnName("ExtraProperties");

3
modules/openiddict/src/Volo.Abp.OpenIddict.AspNetCore/Volo/Abp/OpenIddict/AbpOpenIddictAspNetCoreModule.cs

@ -127,6 +127,9 @@ public class AbpOpenIddictAspNetCoreModule : AbpModule
builder.RemoveEventHandler(OpenIddictServerHandlers.Session.ValidatePostLogoutRedirectUriParameter.Descriptor);
builder.AddEventHandler(AbpValidatePostLogoutRedirectUriParameter.Descriptor);
builder.RemoveEventHandler(OpenIddictServerHandlers.Session.ValidateAuthorizedParty.Descriptor);
builder.AddEventHandler(AbpValidateAuthorizedParty.Descriptor);
}
builder.AddEventHandler(RemoveClaimsFromClientCredentialsGrantType.Descriptor);

9
modules/openiddict/src/Volo.Abp.OpenIddict.AspNetCore/Volo/Abp/OpenIddict/WildcardDomains/AbpOpenIddictWildcardDomainBase.cs

@ -7,19 +7,16 @@ using Volo.Abp.Text.Formatting;
namespace Volo.Abp.OpenIddict.WildcardDomains;
public abstract class AbpOpenIddictWildcardDomainBase<THandler, TContext> : IOpenIddictServerHandler<TContext>
where THandler : class, new()
where THandler : class
where TContext : OpenIddictServerEvents.BaseContext
{
protected THandler Handler { get; set; }
protected AbpOpenIddictWildcardDomainOptions WildcardDomainOptions { get; }
protected AbpOpenIddictWildcardDomainBase(IOptions<AbpOpenIddictWildcardDomainOptions> wildcardDomainOptions, bool initHandler = true)
protected AbpOpenIddictWildcardDomainBase(IOptions<AbpOpenIddictWildcardDomainOptions> wildcardDomainOptions, THandler handler)
{
WildcardDomainOptions = wildcardDomainOptions.Value;
if (initHandler)
{
Handler = new THandler();
}
Handler = handler;
}
public abstract ValueTask HandleAsync(TContext context);

37
modules/openiddict/src/Volo.Abp.OpenIddict.AspNetCore/Volo/Abp/OpenIddict/WildcardDomains/AbpValidateAuthorizedParty.cs

@ -0,0 +1,37 @@
using System.Threading.Tasks;
using Microsoft.Extensions.Options;
using OpenIddict.Abstractions;
using OpenIddict.Server;
namespace Volo.Abp.OpenIddict.WildcardDomains;
public class AbpValidateAuthorizedParty : AbpOpenIddictWildcardDomainBase<OpenIddictServerHandlers.Session.ValidateAuthorizedParty, OpenIddictServerEvents.ValidateLogoutRequestContext>
{
public static OpenIddictServerHandlerDescriptor Descriptor { get; }
= OpenIddictServerHandlerDescriptor.CreateBuilder<OpenIddictServerEvents.ValidateLogoutRequestContext>()
.UseScopedHandler<AbpValidateAuthorizedParty>()
.SetOrder(OpenIddictServerHandlers.Session.ValidateToken.Descriptor.Order + 1_000)
.SetType(OpenIddictServerHandlerType.BuiltIn)
.Build();
public AbpValidateAuthorizedParty(
IOptions<AbpOpenIddictWildcardDomainOptions> wildcardDomainsOptions,
IOpenIddictApplicationManager applicationManager)
: base(wildcardDomainsOptions, new OpenIddictServerHandlers.Session.ValidateAuthorizedParty(applicationManager))
{
Handler = new OpenIddictServerHandlers.Session.ValidateAuthorizedParty(applicationManager);
}
public async override ValueTask HandleAsync(OpenIddictServerEvents.ValidateLogoutRequestContext context)
{
Check.NotNull(context, nameof(context));
Check.NotNull(context.IdentityTokenHintPrincipal, nameof(context.IdentityTokenHintPrincipal));
if (await CheckWildcardDomainAsync(context.PostLogoutRedirectUri))
{
return;
}
await Handler.HandleAsync(context);
}
}

2
modules/openiddict/src/Volo.Abp.OpenIddict.AspNetCore/Volo/Abp/OpenIddict/WildcardDomains/AbpValidateClientPostLogoutRedirectUri.cs

@ -19,7 +19,7 @@ public class AbpValidateClientPostLogoutRedirectUri : AbpOpenIddictWildcardDomai
public AbpValidateClientPostLogoutRedirectUri(
IOptions<AbpOpenIddictWildcardDomainOptions> wildcardDomainsOptions,
IOpenIddictApplicationManager applicationManager)
: base(wildcardDomainsOptions, false)
: base(wildcardDomainsOptions, new OpenIddictServerHandlers.Session.ValidateClientPostLogoutRedirectUri(applicationManager))
{
Handler = new OpenIddictServerHandlers.Session.ValidateClientPostLogoutRedirectUri(applicationManager);
}

2
modules/openiddict/src/Volo.Abp.OpenIddict.AspNetCore/Volo/Abp/OpenIddict/WildcardDomains/AbpValidateClientRedirectUri.cs

@ -18,7 +18,7 @@ public class AbpValidateClientRedirectUri : AbpOpenIddictWildcardDomainBase<Open
public AbpValidateClientRedirectUri(
IOptions<AbpOpenIddictWildcardDomainOptions> wildcardDomainsOptions,
IOpenIddictApplicationManager applicationManager)
: base(wildcardDomainsOptions, false)
: base(wildcardDomainsOptions, new OpenIddictServerHandlers.Authentication.ValidateClientRedirectUri())
{
Handler = new OpenIddictServerHandlers.Authentication.ValidateClientRedirectUri(applicationManager);
}

2
modules/openiddict/src/Volo.Abp.OpenIddict.AspNetCore/Volo/Abp/OpenIddict/WildcardDomains/AbpValidatePostLogoutRedirectUriParameter.cs

@ -14,7 +14,7 @@ public class AbpValidatePostLogoutRedirectUriParameter : AbpOpenIddictWildcardDo
.Build();
public AbpValidatePostLogoutRedirectUriParameter(IOptions<AbpOpenIddictWildcardDomainOptions> wildcardDomainsOptions)
: base(wildcardDomainsOptions)
: base(wildcardDomainsOptions, new OpenIddictServerHandlers.Session.ValidatePostLogoutRedirectUriParameter())
{
}

2
modules/openiddict/src/Volo.Abp.OpenIddict.AspNetCore/Volo/Abp/OpenIddict/WildcardDomains/AbpValidateRedirectUriParameter.cs

@ -14,7 +14,7 @@ public class AbpValidateRedirectUriParameter : AbpOpenIddictWildcardDomainBase<O
.Build();
public AbpValidateRedirectUriParameter(IOptions<AbpOpenIddictWildcardDomainOptions> wildcardDomainsOptions)
: base(wildcardDomainsOptions)
: base(wildcardDomainsOptions, new OpenIddictServerHandlers.Authentication.ValidateRedirectUriParameter())
{
}

2
npm/ng-packs/packages/theme-shared/src/lib/components/index.ts

@ -12,3 +12,5 @@ export * from './toast-container/toast-container.component';
export * from './toast/toast.component';
export * from './password/password.component';
export * from './card/index';
export * from './checkbox/checkbox.component';
export * from './form-input/form-input.component';

BIN
source-code/Volo.Abp.Account.SourceCode/Volo.Abp.Account.SourceCode.zip

Binary file not shown.

BIN
source-code/Volo.Abp.AuditLogging.SourceCode/Volo.Abp.AuditLogging.SourceCode.zip

Binary file not shown.

BIN
source-code/Volo.Abp.BackgroundJobs.SourceCode/Volo.Abp.BackgroundJobs.SourceCode.zip

Binary file not shown.

BIN
source-code/Volo.Abp.BasicTheme.SourceCode/Volo.Abp.BasicTheme.SourceCode.zip

Binary file not shown.

BIN
source-code/Volo.Abp.BlobStoring.Database.SourceCode/Volo.Abp.BlobStoring.Database.SourceCode.zip

Binary file not shown.

BIN
source-code/Volo.Abp.FeatureManagement.SourceCode/Volo.Abp.FeatureManagement.SourceCode.zip

Binary file not shown.

BIN
source-code/Volo.Abp.Identity.SourceCode/Volo.Abp.Identity.SourceCode.zip

Binary file not shown.

BIN
source-code/Volo.Abp.IdentityServer.SourceCode/Volo.Abp.IdentityServer.SourceCode.zip

Binary file not shown.

BIN
source-code/Volo.Abp.OpenIddict.SourceCode/Volo.Abp.OpenIddict.SourceCode.zip

Binary file not shown.

BIN
source-code/Volo.Abp.PermissionManagement.SourceCode/Volo.Abp.PermissionManagement.SourceCode.zip

Binary file not shown.

BIN
source-code/Volo.Abp.SettingManagement.SourceCode/Volo.Abp.SettingManagement.SourceCode.zip

Binary file not shown.

BIN
source-code/Volo.Abp.TenantManagement.SourceCode/Volo.Abp.TenantManagement.SourceCode.zip

Binary file not shown.

BIN
source-code/Volo.Abp.Users.SourceCode/Volo.Abp.Users.SourceCode.zip

Binary file not shown.

BIN
source-code/Volo.Abp.VirtualFileExplorer.SourceCode/Volo.Abp.VirtualFileExplorer.SourceCode.zip

Binary file not shown.

BIN
source-code/Volo.Blogging.SourceCode/Volo.Blogging.SourceCode.zip

Binary file not shown.

BIN
source-code/Volo.ClientSimulation.SourceCode/Volo.ClientSimulation.SourceCode.zip

Binary file not shown.

BIN
source-code/Volo.CmsKit.SourceCode/Volo.CmsKit.SourceCode.zip

Binary file not shown.

BIN
source-code/Volo.Docs.SourceCode/Volo.Docs.SourceCode.zip

Binary file not shown.

5
templates/app/aspnet-core/src/MyCompanyName.MyProjectName.AuthServer/Pages/Index.cshtml

@ -23,7 +23,7 @@
<div class="account-brand p-4 text-center mb-1">
@if (!BrandingProvider.LogoUrl.IsNullOrEmpty())
{
<a class="navbar-brand" href="/" alt="@BrandingProvider.AppName"></a>
<a class="navbar-brand" href="~/" alt="@BrandingProvider.AppName"></a>
}
else
{
@ -69,7 +69,8 @@
<abp-dropdown-menu>
@foreach (var language in Model.Languages)
{
<abp-dropdown-item href="~/Abp/Languages/Switch?culture=@(language.CultureName)&uiCulture=@(language.UiCultureName)&returnUrl=@(System.Net.WebUtility.UrlEncode(Request.GetEncodedPathAndQuery()))">@language.DisplayName</abp-dropdown-item>
var languageUrl = Url.Content($"~/Abp/Languages/Switch?culture={language.CultureName}&uiCulture={language.UiCultureName}&returnUrl={System.Net.WebUtility.UrlEncode(Request.GetEncodedPathAndQuery())}");
<abp-dropdown-item href="@languageUrl">@language.DisplayName</abp-dropdown-item>
}
</abp-dropdown-menu>
}

13
templates/app/aspnet-core/test/MyCompanyName.MyProjectName.EntityFrameworkCore.Tests/EntityFrameworkCore/MyProjectNameEntityFrameworkCoreTestModule.cs

@ -6,7 +6,9 @@ using Microsoft.Extensions.DependencyInjection;
using Volo.Abp;
using Volo.Abp.EntityFrameworkCore;
using Volo.Abp.EntityFrameworkCore.Sqlite;
using Volo.Abp.FeatureManagement;
using Volo.Abp.Modularity;
using Volo.Abp.PermissionManagement;
using Volo.Abp.Uow;
namespace MyCompanyName.MyProjectName.EntityFrameworkCore;
@ -22,7 +24,18 @@ public class MyProjectNameEntityFrameworkCoreTestModule : AbpModule
public override void ConfigureServices(ServiceConfigurationContext context)
{
Configure<FeatureManagementOptions>(options =>
{
options.SaveStaticFeaturesToDatabase = false;
options.IsDynamicFeatureStoreEnabled = false;
});
Configure<PermissionManagementOptions>(options =>
{
options.SaveStaticPermissionsToDatabase = false;
options.IsDynamicPermissionStoreEnabled = false;
});
context.Services.AddAlwaysDisableUnitOfWorkTransaction();
ConfigureInMemorySqlite(context.Services);
}

Loading…
Cancel
Save