Browse Source

Merge branch 'dev' into halil-post-2024-05

pull/19619/head
Halil İbrahim Kalkan 3 years ago
parent
commit
3cce925b0b
  1. 20
      .github/workflows/auto-pr.yml
  2. 2
      abp_io/AbpIoLocalization/AbpIoLocalization/Commercial/Localization/Resources/en.json
  3. 4
      abp_io/AbpIoLocalization/AbpIoLocalization/Commercial/Localization/Resources/tr.json
  4. 4
      common.props
  5. 23
      docs/en/UI/Angular/Modifying-the-Menu.md
  6. 34
      docs/en/UI/Angular/Router-Events.md
  7. 1
      framework/src/Volo.Abp.AspNetCore.Components.Server.Theming/Bundling/BlazorGlobalScriptContributor.cs
  8. 11
      framework/src/Volo.Abp.AspNetCore.Components.Web.Theming/AbpAspNetCoreComponentsWebThemingModule.cs
  9. 8
      framework/src/Volo.Abp.AspNetCore.Components.Web/Volo/Abp/AspNetCore/Components/Web/AbpAuthenticationOptions.cs
  10. 10
      framework/src/Volo.Abp.AspNetCore.Components.Web/Volo/Abp/AspNetCore/Components/Web/ILocalStorageService.cs
  11. 31
      framework/src/Volo.Abp.AspNetCore.Components.Web/Volo/Abp/AspNetCore/Components/Web/LocalStorageService.cs
  12. 59
      framework/src/Volo.Abp.AspNetCore.Components.Web/Volo/Abp/AspNetCore/Components/Web/Security/AbpAuthenticationState.cs
  13. 17
      framework/src/Volo.Abp.AspNetCore.Components.Web/wwwroot/libs/abp/js/authentication-state-listener.js
  14. 2
      framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming/ComponentsComponentsBundleContributor.cs
  15. 10
      framework/src/Volo.Abp.AspNetCore.Components.WebAssembly/Volo/Abp/AspNetCore/Components/WebAssembly/AbpAspNetCoreComponentsWebAssemblyModule.cs
  16. 45
      framework/src/Volo.Abp.AspNetCore.Components.WebAssembly/Volo/Abp/AspNetCore/Components/WebAssembly/ClientProxyExceptionEventHandler.cs
  17. 34
      framework/src/Volo.Abp.AspNetCore.SignalR/Volo/Abp/AspNetCore/SignalR/Authentication/AbpAuthenticationHubFilter.cs
  18. 136
      npm/ng-packs/packages/core/src/lib/services/routes.service.ts
  19. 133
      npm/ng-packs/packages/core/src/lib/tests/routes.service.spec.ts
  20. 2
      templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.WebAssembly/Client/wwwroot/global.js
  21. 6
      templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.Client/Menus/MyProjectNameMenuContributor.cs
  22. 24
      templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.Client/wwwroot/global.js
  23. 24
      templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.WebApp.Client/wwwroot/global.js
  24. 24
      templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.WebApp.Tiered.Client/wwwroot/global.js
  25. 4
      templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor/App.razor
  26. 2
      templates/module/aspnet-core/host/MyCompanyName.MyProjectName.Blazor.Host.Client/wwwroot/global.css
  27. 2
      templates/module/aspnet-core/host/MyCompanyName.MyProjectName.Blazor.Host.Client/wwwroot/global.js
  28. 4
      templates/module/aspnet-core/host/MyCompanyName.MyProjectName.Blazor.Host/App.razor
  29. 5
      templates/module/aspnet-core/host/MyCompanyName.MyProjectName.Blazor.Server.Host/MyProjectNameBlazorHostModule.cs

20
.github/workflows/auto-pr.yml

@ -1,13 +1,13 @@
name: Merge branch dev with rel-8.1
name: Merge branch dev with prerel-8.2
on:
push:
branches:
- rel-8.1
- prerel-8.2
permissions:
contents: read
jobs:
merge-dev-with-rel-8-1:
merge-dev-with-prerel-8-2:
permissions:
contents: write # for peter-evans/create-pull-request to create branch
pull-requests: write # for peter-evans/create-pull-request to create a PR
@ -18,19 +18,19 @@ jobs:
ref: dev
- name: Reset promotion branch
run: |
git fetch origin rel-8.1:rel-8.1
git reset --hard rel-8.1
git fetch origin prerel-8.2:prerel-8.2
git reset --hard prerel-8.2
- name: Create Pull Request
uses: peter-evans/create-pull-request@v3
with:
branch: auto-merge/rel-8-1/${{github.run_number}}
title: Merge branch dev with rel-8.1
body: This PR generated automatically to merge dev with rel-8.1. Please review the changed files before merging to prevent any errors that may occur.
branch: auto-merge/prerel-8-2/${{github.run_number}}
title: Merge branch dev with prerel-8.2
body: This PR generated automatically to merge dev with prerel-8.2. Please review the changed files before merging to prevent any errors that may occur.
reviewers: maliming
token: ${{ github.token }}
- name: Merge Pull Request
env:
GH_TOKEN: ${{ secrets.BOT_SECRET }}
run: |
gh pr review auto-merge/rel-8-1/${{github.run_number}} --approve
gh pr merge auto-merge/rel-8-1/${{github.run_number}} --merge --auto --delete-branch
gh pr review auto-merge/prerel-8-2/${{github.run_number}} --approve
gh pr merge auto-merge/prerel-8-2/${{github.run_number}} --merge --auto --delete-branch

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

@ -1210,7 +1210,7 @@
"RegisterDemo": "Register",
"TrainingDescription": "We are offering the following training packages for who want to get expertise on the ABP Framework and the ABP Commercial.",
"PurchaseDevelopers": "developers",
"LinkExpiredMessage": "The payment link has expired. Please contact us and receive an updated payment link."
"LinkExpiredMessage": "The payment link has expired! Contact us at <a href='mailto:sales@volosoft.com'>sales@volosoft.com</a> to update the link or <a href='https://commercial.abp.io/contact'>click here</a> to navigate to the contact page."
}
}

4
abp_io/AbpIoLocalization/AbpIoLocalization/Commercial/Localization/Resources/tr.json

@ -1140,6 +1140,6 @@
"RemoveBasket": "Sepetten kaldır",
"TrainingPack": "Eğitim paketi",
"TrainingPackDiscount": "Eğitim paketi indirimi",
"LinkExpiredMessage": "Ödeme bağlantısı süresi doldu. Lütfen bizimle iletişime geçin ve güncel bir ödeme bağlantısı alın."
"LinkExpiredMessage": "Ödeme bağlantısının süresi doldu! Bağlantıyı güncellemek için <a href='mailto:sales@volosoft.com'>sales@volosoft.com</a> adresinden bizimle iletişime geçin veya iletişim sayfasına gitmek için <a href='https://commercial.abp.io/contact'>buraya</a> tıklayın."
}
}
}

4
common.props

@ -1,8 +1,8 @@
<Project>
<PropertyGroup>
<LangVersion>latest</LangVersion>
<Version>8.2.0-preview</Version>
<LeptonXVersion>3.2.0-preview</LeptonXVersion>
<Version>8.3.0-preview</Version>
<LeptonXVersion>3.3.0-preview</LeptonXVersion>
<NoWarn>$(NoWarn);CS1591;CS0436</NoWarn>
<PackageIconUrl>https://abp.io/assets/abp_nupkg.png</PackageIconUrl>
<PackageProjectUrl>https://abp.io/</PackageProjectUrl>

23
docs/en/UI/Angular/Modifying-the-Menu.md

@ -152,6 +152,24 @@ import { APP_ROUTE_PROVIDER } from './route.provider';
export class AppModule {}
```
### Singularize Route Item
- `name` property is must be a unique key. If there are multiple items with the same name, the last one will be displayed in the menu.
- If you want to display multiple items in different parent with the same name, you can call the **setSingularizeStatus(false)** method of the `RoutesService` to disable the singularization.
- **This method should be called before adding the routes.**
- To enable the singularization of the names, you can call the **setSingularizeStatus(true) `(default value: true)`** method of the `RoutesService`.
```typescript
import { RoutesService } from '@abp/ng.core';
import { Component } from '@angular/core';
@Component(/* component metadata */)
export class AppComponent {
constructor(private routes: RoutesService) {
routes.setSingularizeStatus(false);
}
}
```
Here is what every property works as:
- `path` is the absolute path of the navigation element.
@ -226,7 +244,7 @@ After adding the `routes` property as described above, the navigation menu looks
## How to Patch or Remove a Navigation Element
The `patch` method of `RoutesService` finds a route by its name and replaces its configuration with the new configuration passed as the second parameter. Similarly, `remove` method finds a route and removes it along with its children.
The `patch` method of `RoutesService` finds a route by its name and replaces its configuration with the new configuration passed as the second parameter. Similarly, `remove` method finds a route and removes it along with its children. Also you can use `removeByParam` method to delete the routes with given properties.
```js
// this.routes is instance of RoutesService
@ -249,6 +267,9 @@ const newHomeRouteConfig: Partial<ABP.Route> = {
this.routes.add([dashboardRouteConfig]);
this.routes.patch('::Menu:Home', newHomeRouteConfig);
this.routes.remove(['Your navigation']);
// or
this.routes.removeByParam({ name: 'Your navigation' });
```
- Moved the _Home_ navigation under the _Administration_ dropdown based on given `parentName`.

34
docs/en/UI/Angular/Router-Events.md

@ -2,9 +2,6 @@
`RouterEvents` is a utility service for filtering specific router events and reacting to them. Please see [this page in Angular docs](https://angular.io/api/router/Event) for available router events.
## Benefit
You can use router events directly and filter them as seen below:
@ -50,14 +47,10 @@ class SomeService {
`RouterEvents` also delivers improved type-safety. In the example above, `navigationFinish$` has inferred type of `Observable<NavigationCancel | NavigationEnd | NavigationError>` whereas it would have `Observable<Event>` when router events are filtered directly.
## Usage
You do not have to provide `RouterEvents` at the module or component level, because it is already **provided in root**. You can inject and start using it immediately in your components.
### How to Get Specific Navigation Events
You can use `getNavigationEvents` to get a stream of navigation events matching given event keys.
@ -85,7 +78,6 @@ class SomeService {
}
```
### How to Get All Navigation Events
You can use `getAllNavigationEvents` to get a stream of all navigation events without passing any keys.
@ -108,6 +100,30 @@ class SomeService {
}
```
### How to get Current and Previous Navigation
You can use `previousNavigation` and `currentNavigation` properties to retrieve navigations in a reactive way.
```ts
previousNavigation: Signal<string>;
currentNavigation: Signal<string>;
```
```ts
import { RouterEvents } from "@abp/ng.core";
@Injectable()
class SomeService {
readonly routerEvents = inject(RouterEvents);
someAction() {
const previousNavUrl = this.routerEvents.previousNavigation();
if (previousNavUrl) {
// perform some action
}
}
}
```
### How to Get Specific Router Events
@ -126,7 +142,6 @@ class SomeService {
}
```
### How to Get All Router Events
You can use `getEvents` to get a stream of all router events without passing any event constructors. This is nothing different from accessing `events` property of `Router` and is added to the service just for convenience.
@ -143,4 +158,3 @@ class SomeService {
constructor(private routerEvents: RouterEvents) {}
}
```

1
framework/src/Volo.Abp.AspNetCore.Components.Server.Theming/Bundling/BlazorGlobalScriptContributor.cs

@ -15,5 +15,6 @@ public class BlazorGlobalScriptContributor : BundleContributor
context.Files.AddIfNotContains("/_framework/blazor.server.js");
}
context.Files.AddIfNotContains("/_content/Volo.Abp.AspNetCore.Components.Web/libs/abp/js/abp.js");
context.Files.AddIfNotContains("/_content/Volo.Abp.AspNetCore.Components.Web/libs/abp/js/authentication-state-listener.js");
}
}

11
framework/src/Volo.Abp.AspNetCore.Components.Web.Theming/AbpAspNetCoreComponentsWebThemingModule.cs

@ -1,4 +1,5 @@
using Volo.Abp.BlazoriseUI;
using Volo.Abp.AspNetCore.Components.Web.Security;
using Volo.Abp.BlazoriseUI;
using Volo.Abp.Modularity;
using Volo.Abp.UI.Navigation;
@ -10,5 +11,11 @@ namespace Volo.Abp.AspNetCore.Components.Web.Theming;
)]
public class AbpAspNetCoreComponentsWebThemingModule : AbpModule
{
public override void ConfigureServices(ServiceConfigurationContext context)
{
Configure<AbpDynamicLayoutComponentOptions>(options =>
{
options.Components.Add(typeof(AbpAuthenticationState), null);
});
}
}

8
framework/src/Volo.Abp.AspNetCore.Components.Web/Volo/Abp/AspNetCore/Components/Web/AbpAuthenticationOptions.cs

@ -0,0 +1,8 @@
namespace Volo.Abp.AspNetCore.Components.Web;
public class AbpAuthenticationOptions
{
public string LoginUrl { get; set; } = "Account/Login";
public string LogoutUrl { get; set; } = "Account/Logout";
}

10
framework/src/Volo.Abp.AspNetCore.Components.Web/Volo/Abp/AspNetCore/Components/Web/ILocalStorageService.cs

@ -0,0 +1,10 @@
using System.Threading.Tasks;
namespace Volo.Abp.AspNetCore.Components.Web;
public interface ILocalStorageService
{
public ValueTask SetItemAsync(string key, string value);
public ValueTask<string> GetItemAsync(string key);
public ValueTask RemoveItemAsync(string key);
}

31
framework/src/Volo.Abp.AspNetCore.Components.Web/Volo/Abp/AspNetCore/Components/Web/LocalStorageService.cs

@ -0,0 +1,31 @@
using System.Threading.Tasks;
using Microsoft.JSInterop;
using Volo.Abp.DependencyInjection;
namespace Volo.Abp.AspNetCore.Components.Web;
[Dependency(ReplaceServices = true)]
public class LocalStorageService : ILocalStorageService, ITransientDependency
{
public IJSRuntime JsRuntime { get; }
public LocalStorageService(IJSRuntime jsRuntime)
{
JsRuntime = jsRuntime;
}
public async ValueTask SetItemAsync(string key, string value)
{
await JsRuntime.InvokeVoidAsync("localStorage.setItem", key, value);
}
public async ValueTask<string> GetItemAsync(string key)
{
return await JsRuntime.InvokeAsync<string>("localStorage.getItem", key);
}
public async ValueTask RemoveItemAsync(string key)
{
await JsRuntime.InvokeVoidAsync("localStorage.removeItem", key);
}
}

59
framework/src/Volo.Abp.AspNetCore.Components.Web/Volo/Abp/AspNetCore/Components/Web/Security/AbpAuthenticationState.cs

@ -0,0 +1,59 @@
using System;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Routing;
using Microsoft.Extensions.Options;
using Volo.Abp.Users;
namespace Volo.Abp.AspNetCore.Components.Web.Security;
public class AbpAuthenticationState : ComponentBase
{
private const string StateKey = "authentication-state-id";
[Inject]
protected ILocalStorageService LocalStorage { get; set; } = default!;
[Inject]
protected ICurrentUser CurrentUser { get; set; } = default!;
[Inject]
protected NavigationManager NavigationManager { get; set; } = default!;
[Inject]
protected IOptions<AbpAuthenticationOptions> AuthenticationOptions { get; set; } = default!;
protected async override Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
{
NavigationManager.RegisterLocationChangingHandler(OnLocationChangingAsync);
if (CurrentUser.IsAuthenticated)
{
await SetAuthenticationStateAsync();
}
else
{
await ClearAuthenticationStateAsync();
}
}
}
protected virtual async ValueTask OnLocationChangingAsync(LocationChangingContext context)
{
if (context.TargetLocation.Contains(AuthenticationOptions.Value.LogoutUrl))
{
await ClearAuthenticationStateAsync();
}
}
protected virtual async Task SetAuthenticationStateAsync()
{
await LocalStorage.SetItemAsync(StateKey, CurrentUser.GetId().ToString());
}
protected virtual async Task ClearAuthenticationStateAsync()
{
await LocalStorage.RemoveItemAsync(StateKey);
}
}

17
framework/src/Volo.Abp.AspNetCore.Components.Web/wwwroot/libs/abp/js/authentication-state-listener.js

@ -0,0 +1,17 @@
(function () {
const stateKey = 'authentication-state-id';
window.addEventListener('storage', function (event) {
if (event.key !== stateKey || event.oldValue === event.newValue) {
return;
}
if (event.oldValue || !event.newValue) {
abp.utils.removeOidcUser();
window.location.reload();
} else {
location.assign('/')
}
});
}());

2
framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming/ComponentsComponentsBundleContributor.cs

@ -9,6 +9,8 @@ public class ComponentsComponentsBundleContributor : IBundleContributor
context.Add("_content/Microsoft.AspNetCore.Components.WebAssembly.Authentication/AuthenticationService.js");
context.Add("_content/Volo.Abp.AspNetCore.Components.Web/libs/abp/js/abp.js");
context.Add("_content/Volo.Abp.AspNetCore.Components.Web/libs/abp/js/lang-utils.js");
context.Add("_content/Volo.Abp.AspNetCore.Components.Web/libs/abp/js/lang-utils.js");
context.Add("_content/Volo.Abp.AspNetCore.Components.Web/libs/abp/js/authentication-state-listener.js");
}
public void AddStyles(BundleContext context)

10
framework/src/Volo.Abp.AspNetCore.Components.WebAssembly/Volo/Abp/AspNetCore/Components/WebAssembly/AbpAspNetCoreComponentsWebAssemblyModule.cs

@ -7,6 +7,7 @@ using Microsoft.AspNetCore.Components.WebAssembly.Authentication;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.Logging;
using Volo.Abp.AspNetCore.Components.Server;
using Volo.Abp.AspNetCore.Components.Web;
using Volo.Abp.AspNetCore.Components.Web.ExceptionHandling;
using Volo.Abp.AspNetCore.Components.Web.Security;
@ -49,6 +50,15 @@ public class AbpAspNetCoreComponentsWebAssemblyModule : AbpModule
context.Services
.GetHostBuilder().Logging
.AddProvider(new AbpExceptionHandlingLoggerProvider(context.Services));
if (!context.Services.ExecutePreConfiguredActions<AbpAspNetCoreComponentsWebOptions>().IsBlazorWebApp)
{
Configure<AbpAuthenticationOptions>(options =>
{
options.LoginUrl = "authentication/login";
options.LogoutUrl = "authentication/logout";
});
}
}
public override void PostConfigureServices(ServiceConfigurationContext context)

45
framework/src/Volo.Abp.AspNetCore.Components.WebAssembly/Volo/Abp/AspNetCore/Components/WebAssembly/ClientProxyExceptionEventHandler.cs

@ -6,6 +6,7 @@ using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using Microsoft.JSInterop;
using Volo.Abp.AspNetCore.Components.Server;
using Volo.Abp.AspNetCore.Components.Web;
using Volo.Abp.DependencyInjection;
using Volo.Abp.EventBus;
using Volo.Abp.Http;
@ -23,32 +24,46 @@ public class ClientProxyExceptionEventHandler : ILocalEventHandler<ClientProxyEx
public virtual async Task HandleEventAsync(ClientProxyExceptionEventData eventData)
{
if (eventData.StatusCode == 401)
using (var scope = ServiceProvider.CreateScope())
{
using (var scope = ServiceProvider.CreateScope())
switch (eventData.StatusCode)
{
var options = scope.ServiceProvider.GetRequiredService<IOptions<AbpAspNetCoreComponentsWebOptions>>();
if (!options.Value.IsBlazorWebApp)
case 401:
{
var navigationManager = scope.ServiceProvider.GetRequiredService<NavigationManager>();
var accessTokenProvider = scope.ServiceProvider.GetRequiredService<IAccessTokenProvider>();
var result = await accessTokenProvider.RequestAccessToken();
if (result.Status != AccessTokenResultStatus.Success)
var options = scope.ServiceProvider.GetRequiredService<IOptions<AbpAspNetCoreComponentsWebOptions>>();
if (!options.Value.IsBlazorWebApp)
{
navigationManager.NavigateToLogout("authentication/logout");
return;
}
var navigationManager = scope.ServiceProvider.GetRequiredService<NavigationManager>();
var accessTokenProvider = scope.ServiceProvider.GetRequiredService<IAccessTokenProvider>();
var authenticationOptions = scope.ServiceProvider.GetRequiredService<IOptions<AbpAuthenticationOptions>>();
var result = await accessTokenProvider.RequestAccessToken();
if (result.Status != AccessTokenResultStatus.Success)
{
navigationManager.NavigateToLogout(authenticationOptions.Value.LogoutUrl);
return;
}
result.TryGetToken(out var token);
if (token != null && DateTimeOffset.Now >= token.Expires.AddMinutes(-5))
result.TryGetToken(out var token);
if (token != null && DateTimeOffset.Now >= token.Expires.AddMinutes(-5))
{
navigationManager.NavigateToLogout(authenticationOptions.Value.LogoutUrl);
}
}
else
{
navigationManager.NavigateToLogout("authentication/logout");
var jsRuntime = scope.ServiceProvider.GetRequiredService<IJSRuntime>();
await jsRuntime.InvokeVoidAsync("eval", "setTimeout(function(){location.assign('/')}, 2000)");
}
break;
}
else
case 403:
{
var jsRuntime = scope.ServiceProvider.GetRequiredService<IJSRuntime>();
await jsRuntime.InvokeVoidAsync("eval", "setTimeout(function(){location.assign('/')}, 2000)");
break;
}
}
}

34
framework/src/Volo.Abp.AspNetCore.SignalR/Volo/Abp/AspNetCore/SignalR/Authentication/AbpAuthenticationHubFilter.cs

@ -13,7 +13,9 @@ public class AbpAuthenticationHubFilter : IHubFilter
public virtual async ValueTask<object?> InvokeMethodAsync(HubInvocationContext invocationContext, Func<HubInvocationContext, ValueTask<object?>> next)
{
var currentPrincipalAccessor = invocationContext.ServiceProvider.GetRequiredService<ICurrentPrincipalAccessor>();
using (currentPrincipalAccessor.Change((await GetDynamicClaimsPrincipalAsync(invocationContext.Context.User, invocationContext.ServiceProvider))!))
var claimsPrincipal = invocationContext.Context.User;
await HandleDynamicClaimsPrincipalAsync(claimsPrincipal, invocationContext.ServiceProvider, invocationContext.Context);
using (currentPrincipalAccessor.Change(claimsPrincipal!))
{
return await next(invocationContext);
}
@ -22,7 +24,9 @@ public class AbpAuthenticationHubFilter : IHubFilter
public virtual async Task OnConnectedAsync(HubLifetimeContext context, Func<HubLifetimeContext, Task> next)
{
var currentPrincipalAccessor = context.ServiceProvider.GetRequiredService<ICurrentPrincipalAccessor>();
using (currentPrincipalAccessor.Change((await GetDynamicClaimsPrincipalAsync(context.Context.User, context.ServiceProvider))!))
var claimsPrincipal = context.Context.User;
await HandleDynamicClaimsPrincipalAsync(claimsPrincipal, context.ServiceProvider, context.Context);
using (currentPrincipalAccessor.Change(claimsPrincipal!))
{
await next(context);
}
@ -31,27 +35,29 @@ public class AbpAuthenticationHubFilter : IHubFilter
public virtual async Task OnDisconnectedAsync(HubLifetimeContext context, Exception? exception, Func<HubLifetimeContext, Exception?, Task> next)
{
var currentPrincipalAccessor = context.ServiceProvider.GetRequiredService<ICurrentPrincipalAccessor>();
using (currentPrincipalAccessor.Change((await GetDynamicClaimsPrincipalAsync(context.Context.User, context.ServiceProvider))!))
var claimsPrincipal = context.Context.User;
await HandleDynamicClaimsPrincipalAsync(claimsPrincipal, context.ServiceProvider, context.Context);
using (currentPrincipalAccessor.Change(claimsPrincipal!))
{
await next(context, exception);
}
}
protected virtual async Task<ClaimsPrincipal?> GetDynamicClaimsPrincipalAsync(ClaimsPrincipal? claimsPrincipal, IServiceProvider serviceProvider)
protected virtual async Task HandleDynamicClaimsPrincipalAsync(ClaimsPrincipal? claimsPrincipal, IServiceProvider serviceProvider, HubCallerContext hubCallerContext)
{
if (claimsPrincipal == null)
{
return claimsPrincipal;
}
if (claimsPrincipal.Identity != null &&
if (claimsPrincipal?.Identity != null &&
claimsPrincipal.Identity.IsAuthenticated &&
serviceProvider.GetRequiredService<IOptions<AbpClaimsPrincipalFactoryOptions>>().Value.IsDynamicClaimsEnabled)
{
var abpClaimsPrincipalFactory = serviceProvider.GetRequiredService<IAbpClaimsPrincipalFactory>();
claimsPrincipal = await abpClaimsPrincipalFactory.CreateDynamicAsync(claimsPrincipal);
}
claimsPrincipal = claimsPrincipal.Identity is ClaimsIdentity identity
? new ClaimsPrincipal(new ClaimsIdentity(claimsPrincipal.Claims, claimsPrincipal.Identity.AuthenticationType, identity.NameClaimType, identity.RoleClaimType))
: new ClaimsPrincipal(new ClaimsIdentity(claimsPrincipal.Claims, claimsPrincipal.Identity.AuthenticationType));
return claimsPrincipal;
claimsPrincipal = await serviceProvider.GetRequiredService<IAbpClaimsPrincipalFactory>().CreateDynamicAsync(claimsPrincipal);
if (claimsPrincipal.Identity?.IsAuthenticated == false)
{
hubCallerContext.Abort();
}
}
}
}

136
npm/ng-packs/packages/core/src/lib/services/routes.service.ts

@ -26,6 +26,7 @@ export abstract class AbstractTreeService<T extends { [key: string | number | sy
private _visible$ = new BehaviorSubject<TreeNode<T>[]>([]);
protected othersGroup: string;
protected shouldSingularizeRoutes = true;
get flat(): T[] {
return this._flat$.value;
@ -51,6 +52,29 @@ export abstract class AbstractTreeService<T extends { [key: string | number | sy
return this._visible$.asObservable();
}
private filterWith(setOrMap: Set<string> | Map<string, T>): T[] {
return this._flat$.value.filter(item => !setOrMap.has(item[this.id]));
}
private findItemsToRemove(set: Set<string>): Set<string> {
return this._flat$.value.reduce((acc, item) => {
if (!acc.has(item[this.parentId])) {
return acc;
}
const childSet = new Set([item[this.id]]);
const children = this.findItemsToRemove(childSet);
return new Set([...acc, ...children]);
}, set);
}
private publish(flatItems: T[]): T[] {
this._flat$.next(flatItems);
this._tree$.next(this.createTree(flatItems));
this._visible$.next(this.createTree(flatItems.filter(item => !this.hide(item))));
return flatItems;
}
protected createTree(items: T[]): TreeNode<T>[] {
return createTreeFromList<T, TreeNode<T>>(
items,
@ -69,57 +93,50 @@ export abstract class AbstractTreeService<T extends { [key: string | number | sy
return Array.from(map, ([key, items]) => ({ group: key, items }));
}
private filterWith(setOrMap: Set<string> | Map<string, T>): T[] {
return this._flat$.value.filter(item => !setOrMap.has(item[this.id]));
}
private findItemsToRemove(set: Set<string>): Set<string> {
return this._flat$.value.reduce((acc, item) => {
if (!acc.has(item[this.parentId])) return acc;
const childSet = new Set([item[this.id]]);
const children = this.findItemsToRemove(childSet);
return new Set([...acc, ...children]);
}, set);
}
private publish(flatItems: T[], visibleItems: T[]): T[] {
this._flat$.next(flatItems);
this._tree$.next(this.createTree(flatItems));
this._visible$.next(this.createTree(visibleItems));
return flatItems;
}
add(items: T[]): T[] {
const map = new Map<string, T>();
items.forEach(item => map.set(item[this.id], item));
let flatItems: T[] = [];
if (!this.shouldSingularizeRoutes) {
flatItems = [...this.flat, ...items];
}
const flatItems = this.filterWith(map);
map.forEach(pushValueTo(flatItems));
if (this.shouldSingularizeRoutes) {
const map = new Map<string, T>();
items.forEach(item => map.set(item[this.id], item));
flatItems = this.filterWith(map);
map.forEach(pushValueTo(flatItems));
}
flatItems.sort(this.sort);
const visibleItems = flatItems.filter(item => !this.hide(item));
return this.publish(flatItems, visibleItems);
return this.publish(flatItems);
}
find(predicate: (item: TreeNode<T>) => boolean, tree = this.tree): TreeNode<T> | null {
return tree.reduce<TreeNode<T> | null>(
(acc, node) => (acc ? acc : predicate(node) ? node : this.find(predicate, node.children)),
null,
);
return tree.reduce<TreeNode<T> | null>((acc, node) => {
if (acc) {
return acc;
}
if (predicate(node)) {
return node;
}
return this.find(predicate, node.children);
}, null);
}
patch(identifier: string, props: Partial<T>): T[] | false {
const flatItems = this._flat$.value;
const index = flatItems.findIndex(item => item[this.id] === identifier);
if (index < 0) return false;
if (index < 0) {
return false;
}
flatItems[index] = { ...flatItems[index], ...props };
flatItems.sort(this.sort);
const visibleItems = flatItems.filter(item => !this.hide(item));
return this.publish(flatItems, visibleItems);
return this.publish(flatItems);
}
refresh(): T[] {
@ -132,23 +149,50 @@ export abstract class AbstractTreeService<T extends { [key: string | number | sy
const setToRemove = this.findItemsToRemove(set);
const flatItems = this.filterWith(setToRemove);
const visibleItems = flatItems.filter(item => !this.hide(item));
return this.publish(flatItems);
}
removeByParam(params: Partial<T>): T[] | null {
if (!params) {
return null;
}
const keys = Object.keys(params) as Array<keyof Partial<T>>;
if (keys.length === 0) {
return null;
}
const excludedList = this.flat.filter(item => keys.every(key => item[key] === params[key]));
if (!excludedList?.length) {
return null;
}
return this.publish(flatItems, visibleItems);
for (const item of excludedList) {
this.removeByParam({ [this.parentId]: item[this.id] } as Partial<T>);
}
const flatItems = this.flat.filter(item => !excludedList.includes(item));
return this.publish(flatItems);
}
search(params: Partial<T>, tree = this.tree): TreeNode<T> | null {
const searchKeys = Object.keys(params) as Array<keyof Partial<T>>;
return tree.reduce<TreeNode<T> | null>(
(acc, node) =>
acc
? acc
: searchKeys.every(key => node[key] === params[key])
? node
: this.search(params, node.children),
null,
);
return tree.reduce<TreeNode<T> | null>((acc, node) => {
if (acc) {
return acc;
}
if (searchKeys.every(key => node[key] === params[key])) {
return node;
}
return this.search(params, node.children);
}, null);
}
setSingularizeStatus(singularize = true): void {
this.shouldSingularizeRoutes = singularize;
}
}
@ -164,7 +208,7 @@ export abstract class AbstractNavTreeService<T extends ABP.Nav>
readonly parentId = 'parentName';
readonly hide = (item: T) => item.invisible || !this.isGranted(item);
readonly sort = (a: T, b: T) => {
return this.compareFunc(a,b)
return this.compareFunc(a, b);
};
constructor(protected injector: Injector) {

133
npm/ng-packs/packages/core/src/lib/tests/routes.service.spec.ts

@ -172,6 +172,64 @@ describe('Routes Service', () => {
});
});
describe('#setSingularizeStatus', () => {
it('should allow to duplicate routes when called with false', () => {
service.setSingularizeStatus(false);
service.add(routes);
const flat = service.flat;
expect(flat.length).toBe(routes.length);
});
it('should allow to duplicate routes with the same name when called with false', () => {
service.setSingularizeStatus(false);
service.add([...routes, { path: '/foo/bar/test', name: 'bar', parentName: 'foo', order: 2 }]);
const flat = service.flat;
expect(flat.length).toBe(routes.length + 1);
});
it('should allow to routes with the same name but different parentName when called with false', () => {
service.setSingularizeStatus(false);
service.add([
{ path: '/foo/bar', name: 'bar', parentName: 'foo', order: 2 },
{ path: '/foo/bar', name: 'bar', parentName: 'baz', order: 1 },
]);
const flat = service.flat;
expect(flat.length).toBe(2);
});
it('should not allow to duplicate routes when called with true', () => {
service.setSingularizeStatus(false);
service.add(routes);
service.setSingularizeStatus(true);
service.add(routes);
const flat = service.flat;
expect(flat.length).toBe(5);
});
it('should not allow to duplicate routes with the same name when called with true', () => {
service.setSingularizeStatus(true);
service.add([...routes, { path: '/foo/bar/test', name: 'bar', parentName: 'any', order: 2 }]);
const flat = service.flat;
expect(flat.length).toBe(5);
});
});
describe('#find', () => {
it('should return node found based on query', () => {
service.add(routes);
@ -242,6 +300,81 @@ describe('Routes Service', () => {
});
});
describe('#removeByParam', () => {
it('should remove route based on given route', () => {
service.add(routes);
service.removeByParam({
name: 'bar',
parentName: 'foo',
});
const flat = service.flat;
expect(flat.length).toBe(2);
const notFound = service.find(route => route.name === 'bar');
expect(notFound).toBe(null);
});
it('should remove if more than one route has the same properties', () => {
service.setSingularizeStatus(false);
service.add([
...routes,
{
path: '/foo/bar',
name: 'bar',
parentName: 'foo',
invisible: true,
order: 2,
breadcrumbText: 'Bar Breadcrumb',
},
]);
service.removeByParam({
path: '/foo/bar',
name: 'bar',
parentName: 'foo',
invisible: true,
order: 2,
breadcrumbText: 'Bar Breadcrumb',
});
const flat = service.flat;
expect(flat.length).toBe(5);
const notFound = service.search({
path: '/foo/bar',
name: 'bar',
parentName: 'foo',
invisible: true,
order: 2,
breadcrumbText: 'Bar Breadcrumb',
});
expect(notFound).toBe(null);
});
it("shouldn't remove if there is no route with the given properties", () => {
service.add(routes);
const flatLengthBeforeRemove = service.flat.length;
service.removeByParam({
name: 'bar',
parentName: 'baz',
});
const flat = service.flat;
expect(flatLengthBeforeRemove - flat.length).toBe(0);
const notFound = service.find(route => route.name === 'bar');
expect(notFound).not.toBe(null);
});
});
describe('#patch', () => {
it('should patch propeties of routes based on given routeNames', () => {
service['isGranted'] = jest.fn(route => route.requiredPolicy !== 'X');

2
templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.WebAssembly/Client/wwwroot/global.js

@ -5,6 +5,8 @@ var abp=abp||{};(function(){abp.utils=abp.utils||{};abp.domReady=function(n){doc
var abp=abp||{};(function(){abp.utils=abp.utils||{};abp.utils.updateHTMLDirAndLangFromLocalStorage=function(){var i=JSON.parse(localStorage.getItem("Abp.IsRtl")),n=document.getElementsByTagName("html")[0],t;n&&(t=localStorage.getItem("Abp.SelectedLanguage"),t&&n.setAttribute("lang",t),i&&n.setAttribute("dir","rtl"))};abp.utils.updateHTMLDirAndLangFromLocalStorage()})();
(function(){const n="authentication-state-id";window.addEventListener("storage",function(t){t.key===n&&t.oldValue!==t.newValue&&(t.oldValue||!t.newValue?(abp.utils.removeOidcUser(),window.location.reload()):location.assign("/"))})})();
/*!
* Bootstrap v5.1.3 (https://getbootstrap.com/)
* Copyright 2011-2021 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)

6
templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.Client/Menus/MyProjectNameMenuContributor.cs

@ -68,13 +68,15 @@ public class MyProjectNameMenuContributor : IMenuContributor
{
var accountStringLocalizer = context.GetLocalizer<AccountResource>();
var authServerUrl = _configuration["AuthServer:Authority"] ?? "";
context.Menu.AddItem(new ApplicationMenuItem(
"Account.Manage",
accountStringLocalizer["MyAccount"],
$"Account/Manage",
$"{authServerUrl.EnsureEndsWith('/')}Account/Manage",
icon: "fa fa-cog",
order: 1000,
null).RequireAuthenticated());
target: "_blank").RequireAuthenticated());
return Task.CompletedTask;
}

24
templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.Client/wwwroot/global.js

File diff suppressed because one or more lines are too long

24
templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.WebApp.Client/wwwroot/global.js

File diff suppressed because one or more lines are too long

24
templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.WebApp.Tiered.Client/wwwroot/global.js

File diff suppressed because one or more lines are too long

4
templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor/App.razor

@ -8,7 +8,7 @@
<base href="/" />
<!--ABP:Styles-->
<link href="global.css?_v=638473199568040519" rel="stylesheet"/>
<link href="global.css?_v=638490521600060525" rel="stylesheet"/>
<link href="main.css" rel="stylesheet"/>
<!--/ABP:Styles-->
<link href="MyCompanyName.MyProjectName.Blazor.Client.styles.css" rel="stylesheet"/>
@ -36,7 +36,7 @@
</div>
<!--ABP:Scripts-->
<script src="global.js?_v=638473199570937070"></script>
<script src="global.js?_v=638490521602018274"></script>
<!--/ABP:Scripts-->
<!-- <TEMPLATE-REMOVE IF-NOT='PWA'> -->

2
templates/module/aspnet-core/host/MyCompanyName.MyProjectName.Blazor.Host.Client/wwwroot/global.css

File diff suppressed because one or more lines are too long

2
templates/module/aspnet-core/host/MyCompanyName.MyProjectName.Blazor.Host.Client/wwwroot/global.js

@ -5,3 +5,5 @@ var abp=abp||{};(function(){abp.utils=abp.utils||{};abp.domReady=function(n){doc
var abp=abp||{};(function(){abp.utils=abp.utils||{};abp.utils.updateHTMLDirAndLangFromLocalStorage=function(){var i=JSON.parse(localStorage.getItem("Abp.IsRtl")),n=document.getElementsByTagName("html")[0],t;n&&(t=localStorage.getItem("Abp.SelectedLanguage"),t&&n.setAttribute("lang",t),i&&n.setAttribute("dir","rtl"))};abp.utils.updateHTMLDirAndLangFromLocalStorage()})();
(function(){const n="authentication-state-id";window.addEventListener("storage",function(t){t.key===n&&t.oldValue!==t.newValue&&(t.oldValue||!t.newValue?(abp.utils.removeOidcUser(),window.location.reload()):location.assign("/"))})})();

4
templates/module/aspnet-core/host/MyCompanyName.MyProjectName.Blazor.Host/App.razor

@ -8,7 +8,7 @@
<base href="/" />
<!--ABP:Styles-->
<link href="global.css?_v=638473199135168410" rel="stylesheet"/>
<link href="global.css?_v=638490522235034880" rel="stylesheet"/>
<link href="main.css" rel="stylesheet"/>
<!--/ABP:Styles-->
<link href="MyCompanyName.MyProjectName.Blazor.Host.Client.styles.css" rel="stylesheet"/>
@ -30,7 +30,7 @@
</div>
<!--ABP:Scripts-->
<script src="global.js?_v=638473199135794271"></script>
<script src="global.js?_v=638490522235601638"></script>
<!--/ABP:Scripts-->
</body>

5
templates/module/aspnet-core/host/MyCompanyName.MyProjectName.Blazor.Server.Host/MyProjectNameBlazorHostModule.cs

@ -134,6 +134,11 @@ public class MyProjectNameBlazorHostModule : AbpModule
options.UseSqlServer();
});
Configure<AbpMultiTenancyOptions>(options =>
{
options.IsEnabled = MultiTenancyConsts.IsEnabled;
});
Configure<AbpBundlingOptions>(options =>
{
// MVC UI

Loading…
Cancel
Save