Browse Source

Merge branch 'dev' into pfx

pull/17512/head
maliming 3 years ago
parent
commit
a280017b04
No known key found for this signature in database GPG Key ID: A646B9CB645ECEA4
  1. 6
      docs/en/Entity-Framework-Core.md
  2. 73
      docs/en/Repositories.md
  3. 5
      framework/src/Volo.Abp.Auditing/Volo/Abp/Auditing/IAuditLogScope.cs
  4. 4
      framework/src/Volo.Abp.Auditing/Volo/Abp/Auditing/IAuditingManager.cs
  5. 3
      framework/src/Volo.Abp.AutoMapper/AutoMapper/AbpAutoMapperExtensibleObjectExtensions.cs
  6. 22
      framework/src/Volo.Abp.Data/Volo/Abp/Data/AbpRepositoryIsReadOnlyException.cs
  7. 1
      framework/src/Volo.Abp.Ddd.Application/Volo/Abp/Application/Services/ApplicationService.cs
  8. 2
      framework/src/Volo.Abp.Ddd.Domain/Microsoft/Extensions/DependencyInjection/ServiceCollectionRepositoryExtensions.cs
  9. 2
      framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/AbpDddDomainModule.cs
  10. 53
      framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/ChangeTracking/ChangeTrackingHelper.cs
  11. 30
      framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/ChangeTracking/ChangeTrackingInterceptor.cs
  12. 22
      framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/ChangeTracking/ChangeTrackingInterceptorRegistrar.cs
  13. 15
      framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/ChangeTracking/DisableEntityChangeTrackingAttribute.cs
  14. 15
      framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/ChangeTracking/EnableEntityChangeTrackingAttribute.cs
  15. 14
      framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/ChangeTracking/EntityChangeTrackingAttribute.cs
  16. 28
      framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Repositories/BasicRepositoryBase.cs
  17. 19
      framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Repositories/EntityChangeTrackingProvider.cs
  18. 10
      framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Repositories/IEntityChangeTrackingProvider.cs
  19. 2
      framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Repositories/IRepository.cs
  20. 4
      framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Repositories/ISupportsExplicitLoading.cs
  21. 50
      framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Repositories/RepositoryExtensions.cs
  22. 35
      framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/Domain/Repositories/EntityFrameworkCore/EfCoreRepository.cs
  23. 2
      framework/test/Volo.Abp.Ddd.Tests/Volo/Abp/Domain/Repositories/RepositoryRegistration_Tests.cs
  24. 168
      framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/ChangeTracking/ChangeTrackingInterceptor_Tests.cs
  25. 41
      framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/Repositories/ReadOnlyRepository_Tests.cs
  26. 4
      modules/blob-storing-database/src/Volo.Abp.BlobStoring.Database.Domain/Volo/Abp/BlobStoring/Database/DatabaseBlob.cs
  27. 4
      modules/blob-storing-database/src/Volo.Abp.BlobStoring.Database.Domain/Volo/Abp/BlobStoring/Database/DatabaseBlobContainer.cs
  28. 4
      modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/Blogs/Blog.cs
  29. 4
      modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/Blogs/BlogFeature.cs
  30. 4
      modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/Menus/MenuItem.cs
  31. 5
      modules/identity/src/Volo.Abp.Identity.Application/Volo/Abp/Identity/IdentityUserAppService.cs
  32. 9
      modules/identity/src/Volo.Abp.Identity.Blazor/Pages/Identity/UserManagement.razor
  33. 3
      modules/identity/src/Volo.Abp.Identity.Blazor/Pages/Identity/UserManagement.razor.cs
  34. 6
      modules/identity/src/Volo.Abp.Identity.Web/Pages/Identity/Users/EditModal.cshtml
  35. 5
      modules/identity/src/Volo.Abp.Identity.Web/Pages/Identity/Users/EditModal.cshtml.cs
  36. 1
      npm/ng-packs/packages/theme-shared/src/lib/handlers/error.handler.ts

6
docs/en/Entity-Framework-Core.md

@ -598,10 +598,14 @@ See also [lazy loading document](https://docs.microsoft.com/en-us/ef/core/queryi
ABP Framework provides read-only [repository](Repositories.md) interfaces (`IReadOnlyRepository<...>` or `IReadOnlyBasicRepository<...>`) to explicitly indicate that your purpose is to query data, but not change it. If so, you can inject these interfaces into your services.
Entity Framework Core read-only repository implementation uses [EF Core's No-Tracking feature](https://learn.microsoft.com/en-us/ef/core/querying/tracking#no-tracking-queries). That means the entities returned from the repository will not be tracked by the EF Core [change tracker](https://learn.microsoft.com/en-us/ef/core/change-tracking/), because it is expected that you won't update entities queried from a read-only repository.
Entity Framework Core read-only repository implementation uses [EF Core's No-Tracking feature](https://learn.microsoft.com/en-us/ef/core/querying/tracking#no-tracking-queries). That means the entities returned from the repository will not be tracked by the EF Core [change tracker](https://learn.microsoft.com/en-us/ef/core/change-tracking/), because it is expected that you won't update entities queried from a read-only repository. If you need to track the entities, you can still use the [AsTracking()](https://learn.microsoft.com/en-us/dotnet/api/microsoft.entityframeworkcore.entityframeworkqueryableextensions.astracking) extension method on the LINQ expression, or `EnableTracking()` extension method on the repository object (See *Enabling / Disabling the Change Tracking* section in this document).
> This behavior works only if the repository object is injected with one of the read-only repository interfaces (`IReadOnlyRepository<...>` or `IReadOnlyBasicRepository<...>`). It won't work if you have injected a standard repository (e.g. `IRepository<...>`) then casted it to a read-only repository interface.
## Enabling / Disabling the Change Tracking
In addition to the read-only repositories, ABP allows to manually control the change tracking behavior for querying objects. Please see the *Enabling / Disabling the Change Tracking* section of the [Repositories documentation](Repositories.md) to learn how to use it.
## Access to the EF Core API
In most cases, you want to hide EF Core APIs behind a repository (this is the main purpose of the repository pattern). However, if you want to access the `DbContext` instance over the repository, you can use `GetDbContext()` or `GetDbSet()` extension methods. Example:

73
docs/en/Repositories.md

@ -176,6 +176,77 @@ Some features (like soft-delete, multi-tenancy and audit logging) won't work, so
The `EnsureExistsAsync` extension method accepts entity id or entities query expression to ensure entities exist, otherwise, it will throw `EntityNotFoundException`.
### Enabling / Disabling the Change Tracking
ABP provides repository extension methods and attributes those can be used to control the change tracking behavior for queried entities in the underlying database provider.
Disabling change tracking can gain performance if you query many entities from the database for read-only purposes. Querying single or a few entities won't make much performance difference, but you are free to use it whenever you like.
> If the underlying database provider doesn't support change tracking, then this system won't have any effect. [Entity Framework Core](Entity-Framework-Core.md) supports change tracking, for example, while the [MongoDB](MongoDB.md) provider doesn't support it.
#### Repository Extension Methods for Change Tracking
Change tracking is enabled unless you explicitly disable it.
**Example: Using the `DisableTracking` extension method**
````csharp
public class MyDemoService : ApplicationService
{
private readonly IRepository<Person, Guid> _personRepository;
public MyDemoService(IRepository<Person, Guid> personRepository)
{
_personRepository = personRepository;
}
public async Task DoItAsync()
{
// Change tracking is enabled in that point (by default)
using (_personRepository.DisableTracking())
{
// Change tracking is disabled in that point
var list = await _personRepository.GetPagedListAsync(0, 100, "Name ASC");
}
// Change tracking is enabled in that point (by default)
}
}
````
> `DisableTracking` extension method returns a `IDisposable` object, so you can safely **restore** the change tracking behavior to the **previous state** one the `using` block ends. Basically, `DisableTracking` method ensures that the change tracking is disabled inside the `using` block, but doesn't affect outside of the `using` block. That means, if change tracking was already disabled, `DisableTracking` and the disposable return value do nothing.
`EnableTracking()` method works exactly opposite to the `DisableTracking()` method. You typically won't use it (because the change tracking is already enabled by default), but it is there in case of you need that.
#### Attributes for Change Tracking
You typically use the `DisableTracking()` method for the application service methods those only returns data, but doesn't make any change on entities. For such cases, you can use the `DisableEntityChangeTracking` attribute on your method/class as a shortcut to disable the change tracking for whole method body.
**Example: Using the `DisableEntityChangeTracking` attribute on a method**
````csharp
[DisableEntityChangeTracking]
public virtual async Task<List<PersonDto>> GetListAsync()
{
/* We disabled the change tracking in this method
because we won't change the people objects */
var people = await _personRepository.GetListAsync();
return ObjectMapper.Map<List<Person>, List<PersonDto>(people);
}
````
`EnableEntityChangeTracking` can be used for the opposite purpose, and it ensures that the change tracking is enabled for a given method. Since the change tracking is enabled by default, `EnableEntityChangeTracking` may be needed only if you know that your method is called from a context that disables the change tracking.
`DisableEntityChangeTracking` and `EnableEntityChangeTracking` attributes can be used on a **method** or on a **class** (which affects all of the class methods).
ABP uses dynamic proxying to make these attributes working. There are some rules here:
* If you are **not injecting** the service over an interface (like `IPersonAppService`), then the methods of the service must be `virtual`. Otherwise, [dynamic proxy / interception](Dynamic-Proxying-Interceptors.md) system can not work.
* Only `async` methods (methods returning a `Task` or `Task<T>`) are intercepted.
> Change tracking behavior doesn't affect tracking entity objects returned from `InsertAsync` and `UpdateAsync` methods. The objects returned from these methods are always tracked (if the underlying provider has the change tracking feature) and any change you made to these objects are saved into the database.
## Other Generic Repository Types
Standard `IRepository<TEntity, TKey>` interface exposes the standard `IQueryable<TEntity>` and you can freely query using the standard LINQ methods. This is fine for most of the applications. However, some ORM providers or database systems may not support standard `IQueryable` interface. If you want to use such providers, you can't rely on the `IQueryable`.
@ -217,7 +288,7 @@ They can all be seen as below:
#### Read Only Repositories behavior in Entity Framework Core
Entity Framework Core read-only repository implementation uses [EF Core's No-Tracking feature](https://learn.microsoft.com/en-us/ef/core/querying/tracking#no-tracking-queries). That means the entities returned from the repository will not be tracked by the EF Core [change tracker](https://learn.microsoft.com/en-us/ef/core/change-tracking/), because it is expected that you won't update entities queried from a read-only repository. If you need to track the entities, you can still uses [AsTracking()](https://learn.microsoft.com/en-us/dotnet/api/microsoft.entityframeworkcore.entityframeworkqueryableextensions.astracking) extension method.
Entity Framework Core read-only repository implementation uses [EF Core's No-Tracking feature](https://learn.microsoft.com/en-us/ef/core/querying/tracking#no-tracking-queries). That means the entities returned from the repository will not be tracked by the EF Core [change tracker](https://learn.microsoft.com/en-us/ef/core/change-tracking/), because it is expected that you won't update entities queried from a read-only repository. If you need to track the entities, you can still use the [AsTracking()](https://learn.microsoft.com/en-us/dotnet/api/microsoft.entityframeworkcore.entityframeworkqueryableextensions.astracking) extension method on the LINQ expression, or `EnableTracking()` extension method on the repository object (See *Enabling / Disabling the Change Tracking* section in this document).
> This behavior works only if the repository object is injected with one of the read-only repository interfaces (`IReadOnlyRepository<...>` or `IReadOnlyBasicRepository<...>`). It won't work if you have injected a standard repository (e.g. `IRepository<...>`) then casted it to a read-only repository interface.

5
framework/src/Volo.Abp.Auditing/Volo/Abp/Auditing/IAuditLogScope.cs

@ -1,9 +1,6 @@
using JetBrains.Annotations;
namespace Volo.Abp.Auditing;
namespace Volo.Abp.Auditing;
public interface IAuditLogScope
{
[NotNull]
AuditLogInfo Log { get; }
}

4
framework/src/Volo.Abp.Auditing/Volo/Abp/Auditing/IAuditingManager.cs

@ -1,6 +1,4 @@
using JetBrains.Annotations;
namespace Volo.Abp.Auditing;
namespace Volo.Abp.Auditing;
public interface IAuditingManager
{

3
framework/src/Volo.Abp.AutoMapper/AutoMapper/AbpAutoMapperExtensibleDtoExtensions.cs → framework/src/Volo.Abp.AutoMapper/AutoMapper/AbpAutoMapperExtensibleObjectExtensions.cs

@ -1,12 +1,11 @@
using System.Collections.Generic;
using Volo.Abp;
using Volo.Abp.AutoMapper;
using Volo.Abp.Data;
using Volo.Abp.ObjectExtending;
namespace AutoMapper;
public static class AbpAutoMapperExtensibleDtoExtensions
public static class AbpAutoMapperExtensibleObjectExtensions
{
public static IMappingExpression<TSource, TDestination> MapExtraProperties<TSource, TDestination>(
this IMappingExpression<TSource, TDestination> mappingExpression,

22
framework/src/Volo.Abp.Data/Volo/Abp/Data/AbpRepositoryIsReadOnlyException.cs

@ -1,22 +0,0 @@
namespace Volo.Abp.Data;
public class AbpRepositoryIsReadOnlyException : AbpException
{
/// <summary>
/// Creates a new <see cref="AbpRepositoryIsReadOnlyException"/> object.
/// </summary>
public AbpRepositoryIsReadOnlyException()
{
}
/// <summary>
/// Creates a new <see cref="AbpRepositoryIsReadOnlyException"/> object.
/// </summary>
/// <param name="message">Exception message</param>
public AbpRepositoryIsReadOnlyException(string message)
: base(message)
{
}
}

1
framework/src/Volo.Abp.Ddd.Application/Volo/Abp/Application/Services/ApplicationService.cs

@ -1,4 +1,3 @@
using JetBrains.Annotations;
using Microsoft.AspNetCore.Authorization;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Localization;

2
framework/src/Volo.Abp.Ddd.Domain/Microsoft/Extensions/DependencyInjection/ServiceCollectionRepositoryExtensions.cs

@ -92,7 +92,7 @@ public static class ServiceCollectionRepositoryExtensions
descriptor = ServiceDescriptor.Transient(serviceType, provider =>
{
var repository = provider.GetRequiredService(implementationType);
ObjectHelper.TrySetProperty(repository.As<IRepository>(), x => x.IsReadOnly, _ => true);
ObjectHelper.TrySetProperty(repository.As<IRepository>(), x => x.IsChangeTrackingEnabled, _ => false);
return repository;
});
}

2
framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/AbpDddDomainModule.cs

@ -2,6 +2,7 @@
using Volo.Abp.Auditing;
using Volo.Abp.Caching;
using Volo.Abp.Data;
using Volo.Abp.Domain.ChangeTracking;
using Volo.Abp.Domain.Repositories;
using Volo.Abp.EventBus;
using Volo.Abp.ExceptionHandling;
@ -30,5 +31,6 @@ public class AbpDddDomainModule : AbpModule
public override void PreConfigureServices(ServiceConfigurationContext context)
{
context.Services.AddConventionalRegistrar(new AbpRepositoryConventionalRegistrar());
context.Services.OnRegistered(ChangeTrackingInterceptorRegistrar.RegisterIfNeeded);
}
}

53
framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/ChangeTracking/ChangeTrackingHelper.cs

@ -0,0 +1,53 @@
using System.Linq;
using System.Reflection;
using JetBrains.Annotations;
using Volo.Abp.Domain.Repositories;
namespace Volo.Abp.Domain.ChangeTracking;
public static class ChangeTrackingHelper
{
public static bool IsEntityChangeTrackingType(TypeInfo implementationType)
{
return HasEntityChangeTrackingAttribute(implementationType) || AnyMethodHasEntityChangeTrackingAttribute(implementationType);
}
public static bool IsEntityChangeTrackingMethod([NotNull] MethodInfo methodInfo, out EntityChangeTrackingAttribute? entityChangeTrackingAttribute)
{
Check.NotNull(methodInfo, nameof(methodInfo));
//Method declaration
var attrs = methodInfo.GetCustomAttributes(true).OfType<EntityChangeTrackingAttribute>().ToArray();
if (attrs.Any())
{
entityChangeTrackingAttribute = attrs.First();
return true;
}
if (methodInfo.DeclaringType != null)
{
//Class declaration
attrs = methodInfo.DeclaringType.GetTypeInfo().GetCustomAttributes(true).OfType<EntityChangeTrackingAttribute>().ToArray();
if (attrs.Any())
{
entityChangeTrackingAttribute = attrs.First();
return true;
}
}
entityChangeTrackingAttribute = null;
return false;
}
private static bool AnyMethodHasEntityChangeTrackingAttribute(TypeInfo implementationType)
{
return implementationType
.GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)
.Any(HasEntityChangeTrackingAttribute);
}
private static bool HasEntityChangeTrackingAttribute(MemberInfo memberInfo)
{
return memberInfo.IsDefined(typeof(EntityChangeTrackingAttribute), true);
}
}

30
framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/ChangeTracking/ChangeTrackingInterceptor.cs

@ -0,0 +1,30 @@
using System.Threading.Tasks;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Domain.Repositories;
using Volo.Abp.DynamicProxy;
namespace Volo.Abp.Domain.ChangeTracking;
public class ChangeTrackingInterceptor : AbpInterceptor, ITransientDependency
{
private readonly IEntityChangeTrackingProvider _entityChangeTrackingProvider;
public ChangeTrackingInterceptor(IEntityChangeTrackingProvider entityChangeTrackingProvider)
{
_entityChangeTrackingProvider = entityChangeTrackingProvider;
}
public async override Task InterceptAsync(IAbpMethodInvocation invocation)
{
if (!ChangeTrackingHelper.IsEntityChangeTrackingMethod(invocation.Method, out var changeTrackingAttribute))
{
await invocation.ProceedAsync();
return;
}
using (_entityChangeTrackingProvider.Change(changeTrackingAttribute?.IsEnabled))
{
await invocation.ProceedAsync();
}
}
}

22
framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/ChangeTracking/ChangeTrackingInterceptorRegistrar.cs

@ -0,0 +1,22 @@
using System;
using System.Reflection;
using Volo.Abp.DependencyInjection;
using Volo.Abp.DynamicProxy;
namespace Volo.Abp.Domain.ChangeTracking;
public class ChangeTrackingInterceptorRegistrar
{
public static void RegisterIfNeeded(IOnServiceRegistredContext context)
{
if (ShouldIntercept(context.ImplementationType))
{
context.Interceptors.TryAdd<ChangeTrackingInterceptor>();
}
}
private static bool ShouldIntercept(Type type)
{
return !DynamicProxyIgnoreTypes.Contains(type) && ChangeTrackingHelper.IsEntityChangeTrackingType(type.GetTypeInfo());
}
}

15
framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/ChangeTracking/DisableEntityChangeTrackingAttribute.cs

@ -0,0 +1,15 @@
using System;
namespace Volo.Abp.Domain.ChangeTracking;
/// <summary>
/// Ensures that the change tracking in enabled for the given method or class.
/// </summary>
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class)]
public class DisableEntityChangeTrackingAttribute : EntityChangeTrackingAttribute
{
public DisableEntityChangeTrackingAttribute()
: base(false)
{
}
}

15
framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/ChangeTracking/EnableEntityChangeTrackingAttribute.cs

@ -0,0 +1,15 @@
using System;
namespace Volo.Abp.Domain.ChangeTracking;
/// <summary>
/// Ensures that the change tracking in enabled for the given method or class.
/// </summary>
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class)]
public class EnableEntityChangeTrackingAttribute : EntityChangeTrackingAttribute
{
public EnableEntityChangeTrackingAttribute()
: base(true)
{
}
}

14
framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/ChangeTracking/EntityChangeTrackingAttribute.cs

@ -0,0 +1,14 @@
using System;
namespace Volo.Abp.Domain.ChangeTracking;
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class)]
public abstract class EntityChangeTrackingAttribute : Attribute
{
public virtual bool IsEnabled { get; set; }
public EntityChangeTrackingAttribute(bool isEnabled)
{
IsEnabled = isEnabled;
}
}

28
framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Repositories/BasicRepositoryBase.cs

@ -4,6 +4,8 @@ using System.Collections.Generic;
using System.Linq.Expressions;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
using Volo.Abp.Data;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Domain.Entities;
@ -34,7 +36,13 @@ public abstract class BasicRepositoryBase<TEntity> :
public ICancellationTokenProvider CancellationTokenProvider => LazyServiceProvider.LazyGetService<ICancellationTokenProvider>(NullCancellationTokenProvider.Instance);
public bool IsReadOnly { get; protected set; }
public ILoggerFactory? LoggerFactory => LazyServiceProvider.LazyGetService<ILoggerFactory>();
public ILogger Logger => LazyServiceProvider.LazyGetService<ILogger>(provider => LoggerFactory?.CreateLogger(GetType().FullName!) ?? NullLogger.Instance);
public IEntityChangeTrackingProvider EntityChangeTrackingProvider => LazyServiceProvider.LazyGetRequiredService<IEntityChangeTrackingProvider>();
public bool? IsChangeTrackingEnabled { get; protected set; }
protected BasicRepositoryBase()
{
@ -108,6 +116,24 @@ public abstract class BasicRepositoryBase<TEntity> :
{
return CancellationTokenProvider.FallbackToProvider(preferredValue);
}
protected virtual bool ShouldTrackingEntityChange()
{
// If IsChangeTrackingEnabled is set, it has the highest priority. This generally means the repository is read-only.
if (IsChangeTrackingEnabled.HasValue)
{
return IsChangeTrackingEnabled.Value;
}
// If Interface/Class/Method has Enable/DisableEntityChangeTrackingAttribute, it has the second highest priority.
if (EntityChangeTrackingProvider.Enabled.HasValue)
{
return EntityChangeTrackingProvider.Enabled.Value;
}
// Default behavior is tracking entity change.
return true;
}
}
public abstract class BasicRepositoryBase<TEntity, TKey> : BasicRepositoryBase<TEntity>, IBasicRepository<TEntity, TKey>

19
framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Repositories/EntityChangeTrackingProvider.cs

@ -0,0 +1,19 @@
using System;
using System.Threading;
using Volo.Abp.DependencyInjection;
namespace Volo.Abp.Domain.Repositories;
public class EntityChangeTrackingProvider : IEntityChangeTrackingProvider, ISingletonDependency
{
public bool? Enabled => _current.Value;
private readonly AsyncLocal<bool?> _current = new AsyncLocal<bool?>();
public IDisposable Change(bool? enabled)
{
var previousValue = Enabled;
_current.Value = enabled;
return new DisposeAction(() => _current.Value = previousValue);
}
}

10
framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Repositories/IEntityChangeTrackingProvider.cs

@ -0,0 +1,10 @@
using System;
namespace Volo.Abp.Domain.Repositories;
public interface IEntityChangeTrackingProvider
{
bool? Enabled { get; }
IDisposable Change(bool? enabled);
}

2
framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Repositories/IRepository.cs

@ -12,7 +12,7 @@ namespace Volo.Abp.Domain.Repositories;
/// </summary>
public interface IRepository
{
bool IsReadOnly { get; }
bool? IsChangeTrackingEnabled { get; }
}
public interface IRepository<TEntity> : IReadOnlyRepository<TEntity>, IBasicRepository<TEntity>

4
framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Repositories/ISupportsExplicitLoading.cs

@ -7,8 +7,8 @@ using Volo.Abp.Domain.Entities;
namespace Volo.Abp.Domain.Repositories;
public interface ISupportsExplicitLoading<TEntity, TKey>
where TEntity : class, IEntity<TKey>
public interface ISupportsExplicitLoading<TEntity>
where TEntity : class, IEntity
{
Task EnsureCollectionLoadedAsync<TProperty>(
TEntity entity,

50
framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Repositories/RepositoryExtensions.cs

@ -15,16 +15,16 @@ namespace Volo.Abp.Domain.Repositories;
public static class RepositoryExtensions
{
public async static Task EnsureCollectionLoadedAsync<TEntity, TKey, TProperty>(
this IBasicRepository<TEntity, TKey> repository,
public async static Task EnsureCollectionLoadedAsync<TEntity, TProperty>(
this IBasicRepository<TEntity> repository,
TEntity entity,
Expression<Func<TEntity, IEnumerable<TProperty>>> propertyExpression,
CancellationToken cancellationToken = default
)
where TEntity : class, IEntity<TKey>
where TEntity : class, IEntity
where TProperty : class
{
var repo = ProxyHelper.UnProxy(repository) as ISupportsExplicitLoading<TEntity, TKey>;
var repo = ProxyHelper.UnProxy(repository) as ISupportsExplicitLoading<TEntity>;
if (repo != null)
{
await repo.EnsureCollectionLoadedAsync(entity, propertyExpression, cancellationToken);
@ -40,7 +40,7 @@ public static class RepositoryExtensions
where TEntity : class, IEntity<TKey>
where TProperty : class
{
var repo = ProxyHelper.UnProxy(repository) as ISupportsExplicitLoading<TEntity, TKey>;
var repo = ProxyHelper.UnProxy(repository) as ISupportsExplicitLoading<TEntity>;
if (repo != null)
{
await repo.EnsurePropertyLoadedAsync(entity, propertyExpression, cancellationToken);
@ -60,12 +60,12 @@ public static class RepositoryExtensions
}
}
public async static Task EnsureExistsAsync<TEntity, TKey>(
this IRepository<TEntity, TKey> repository,
public async static Task EnsureExistsAsync<TEntity>(
this IRepository<TEntity> repository,
Expression<Func<TEntity, bool>> expression,
CancellationToken cancellationToken = default
)
where TEntity : class, IEntity<TKey>
where TEntity : class, IEntity
{
if (!await repository.AnyAsync(expression, cancellationToken))
{
@ -145,6 +145,40 @@ public static class RepositoryExtensions
}
}
/// <summary>
/// Disables change tracking mechanism for the given repository.
/// </summary>
/// <param name="repository">A repository object</param>
/// <returns>
/// A disposable object. Dispose it to restore change tracking mechanism back to its previous state.
/// </returns>
public static IDisposable DisableTracking(this IRepository repository)
{
return Tracking(repository, false);
}
/// <summary>
/// Enables change tracking mechanism for the given repository.
/// </summary>
/// <param name="repository">A repository object</param>
/// <returns>
/// A disposable object. Dispose it to restore change tracking mechanism back to its previous state.
/// </returns>
public static IDisposable EnableTracking(this IRepository repository)
{
return Tracking(repository, true);
}
private static IDisposable Tracking(this IRepository repository, bool enabled)
{
var previous = repository.IsChangeTrackingEnabled;
ObjectHelper.TrySetProperty(ProxyHelper.UnProxy(repository).As<IRepository>(), x => x.IsChangeTrackingEnabled, _ => enabled);
return new DisposeAction<IRepository>(_ =>
{
ObjectHelper.TrySetProperty(ProxyHelper.UnProxy(repository).As<IRepository>(), x => x.IsChangeTrackingEnabled, _ => previous);
}, repository);
}
private static IUnitOfWorkManager GetUnitOfWorkManager<TEntity>(
this IBasicRepository<TEntity> repository,
[CallerMemberName] string callingMethodName = nameof(GetUnitOfWorkManager)

35
framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/Domain/Repositories/EntityFrameworkCore/EfCoreRepository.cs

@ -9,6 +9,7 @@ using System.Linq.Expressions;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore.Storage;
using Microsoft.Extensions.Logging;
using Volo.Abp.Data;
using Volo.Abp.Domain.Entities;
using Volo.Abp.EntityFrameworkCore;
@ -107,7 +108,6 @@ public class EfCoreRepository<TDbContext, TEntity> : RepositoryBase<TEntity>, IE
public async override Task<TEntity> InsertAsync(TEntity entity, bool autoSave = false, CancellationToken cancellationToken = default)
{
CheckReadOnly();
CheckAndSetId(entity);
var dbContext = await GetDbContextAsync();
@ -124,7 +124,6 @@ public class EfCoreRepository<TDbContext, TEntity> : RepositoryBase<TEntity>, IE
public async override Task InsertManyAsync(IEnumerable<TEntity> entities, bool autoSave = false, CancellationToken cancellationToken = default)
{
CheckReadOnly();
var entityArray = entities.ToArray();
if (entityArray.IsNullOrEmpty())
{
@ -160,7 +159,6 @@ public class EfCoreRepository<TDbContext, TEntity> : RepositoryBase<TEntity>, IE
public async override Task<TEntity> UpdateAsync(TEntity entity, bool autoSave = false, CancellationToken cancellationToken = default)
{
CheckReadOnly();
var dbContext = await GetDbContextAsync();
dbContext.Attach(entity);
@ -183,8 +181,6 @@ public class EfCoreRepository<TDbContext, TEntity> : RepositoryBase<TEntity>, IE
return;
}
CheckReadOnly();
cancellationToken = GetCancellationToken(cancellationToken);
if (BulkOperationProvider != null)
@ -211,7 +207,6 @@ public class EfCoreRepository<TDbContext, TEntity> : RepositoryBase<TEntity>, IE
public async override Task DeleteAsync(TEntity entity, bool autoSave = false, CancellationToken cancellationToken = default)
{
CheckReadOnly();
var dbContext = await GetDbContextAsync();
dbContext.Set<TEntity>().Remove(entity);
@ -230,7 +225,6 @@ public class EfCoreRepository<TDbContext, TEntity> : RepositoryBase<TEntity>, IE
return;
}
CheckReadOnly();
cancellationToken = GetCancellationToken(cancellationToken);
if (BulkOperationProvider != null)
@ -294,12 +288,12 @@ public class EfCoreRepository<TDbContext, TEntity> : RepositoryBase<TEntity>, IE
[Obsolete("Use GetQueryableAsync method.")]
protected override IQueryable<TEntity> GetQueryable()
{
return DbSet.AsQueryable().AsNoTrackingIf(IsReadOnly);
return DbSet.AsQueryable().AsNoTrackingIf(!ShouldTrackingEntityChange());
}
public async override Task<IQueryable<TEntity>> GetQueryableAsync()
{
return (await GetDbSetAsync()).AsQueryable().AsNoTrackingIf(IsReadOnly);
return (await GetDbSetAsync()).AsQueryable().AsNoTrackingIf(!ShouldTrackingEntityChange());
}
protected async override Task SaveChangesAsync(CancellationToken cancellationToken)
@ -323,7 +317,6 @@ public class EfCoreRepository<TDbContext, TEntity> : RepositoryBase<TEntity>, IE
public async override Task DeleteAsync(Expression<Func<TEntity, bool>> predicate, bool autoSave = false, CancellationToken cancellationToken = default)
{
CheckReadOnly();
var dbContext = await GetDbContextAsync();
var dbSet = dbContext.Set<TEntity>();
@ -341,7 +334,6 @@ public class EfCoreRepository<TDbContext, TEntity> : RepositoryBase<TEntity>, IE
public async override Task DeleteDirectAsync(Expression<Func<TEntity, bool>> predicate, CancellationToken cancellationToken = default)
{
CheckReadOnly();
var dbContext = await GetDbContextAsync();
var dbSet = dbContext.Set<TEntity>();
await dbSet.Where(predicate).ExecuteDeleteAsync(GetCancellationToken(cancellationToken));
@ -445,26 +437,11 @@ public class EfCoreRepository<TDbContext, TEntity> : RepositoryBase<TEntity>, IE
true
);
}
protected virtual void CheckReadOnly()
{
if (IsReadOnly)
{
throw new AbpRepositoryIsReadOnlyException($"Can not call " +
$"{nameof(InsertAsync)}, " +
$"{nameof(InsertManyAsync)}, " +
$"{nameof(UpdateAsync)}, " +
$"{nameof(UpdateManyAsync)}, " +
$"{nameof(DeleteAsync)}, " +
$"{nameof(DeleteManyAsync)}, " +
$"{nameof(DeleteDirectAsync)} methods on a read-only repository!");
}
}
}
public class EfCoreRepository<TDbContext, TEntity, TKey> : EfCoreRepository<TDbContext, TEntity>,
IEfCoreRepository<TEntity, TKey>,
ISupportsExplicitLoading<TEntity, TKey>
ISupportsExplicitLoading<TEntity>
where TDbContext : IEfCoreDbContext
where TEntity : class, IEntity<TKey>
@ -491,14 +468,13 @@ public class EfCoreRepository<TDbContext, TEntity, TKey> : EfCoreRepository<TDbC
{
return includeDetails
? await (await WithDetailsAsync()).OrderBy(e => e.Id).FirstOrDefaultAsync(e => e.Id.Equals(id), GetCancellationToken(cancellationToken))
: IsReadOnly
: !ShouldTrackingEntityChange()
? await (await GetQueryableAsync()).OrderBy(e => e.Id).FirstOrDefaultAsync(e => e.Id.Equals(id), GetCancellationToken(cancellationToken))
: await (await GetDbSetAsync()).FindAsync(new object[] {id}, GetCancellationToken(cancellationToken));
}
public virtual async Task DeleteAsync(TKey id, bool autoSave = false, CancellationToken cancellationToken = default)
{
CheckReadOnly();
var entity = await FindAsync(id, cancellationToken: cancellationToken);
if (entity == null)
{
@ -510,7 +486,6 @@ public class EfCoreRepository<TDbContext, TEntity, TKey> : EfCoreRepository<TDbC
public virtual async Task DeleteManyAsync(IEnumerable<TKey> ids, bool autoSave = false, CancellationToken cancellationToken = default)
{
CheckReadOnly();
cancellationToken = GetCancellationToken(cancellationToken);
var entities = await (await GetDbSetAsync()).Where(x => ids.Contains(x.Id)).ToListAsync(cancellationToken);

2
framework/test/Volo.Abp.Ddd.Tests/Volo/Abp/Domain/Repositories/RepositoryRegistration_Tests.cs

@ -407,7 +407,7 @@ public class RepositoryRegistration_Tests
public class MyTestAggregateRootWithDefaultPkEmptyRepository : IMyTestAggregateRootWithDefaultPkEmptyRepository
{
public bool IsReadOnly { get; set; }
public bool? IsChangeTrackingEnabled { get; set; }
}
public class TestDbContextRegistrationOptions : AbpCommonDbContextRegistrationOptions

168
framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/ChangeTracking/ChangeTrackingInterceptor_Tests.cs

@ -0,0 +1,168 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Shouldly;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Domain.ChangeTracking;
using Volo.Abp.Domain.Repositories;
using Volo.Abp.TestApp.Domain;
using Volo.Abp.TestApp.Testing;
using Xunit;
namespace Volo.Abp.EntityFrameworkCore.ChangeTracking;
public class ChangeTrackingInterceptor_Tests : TestAppTestBase<AbpEntityFrameworkCoreTestModule>
{
[Fact]
public async Task ReadOnly_Repository_Should_Not_Track_Entities()
{
await AddSomePeopleAsync();
var readOnlyRepository = GetRequiredService<IReadOnlyRepository<Person, Guid>>();
await WithUnitOfWorkAsync(async () =>
{
var db = await readOnlyRepository.GetDbContextAsync();
db.ChangeTracker.Entries().Count().ShouldBe(0);
var service = GetRequiredService<MyReadOnlyService>();
var list = await service.GetPeoplesAsync();
list.Count.ShouldBeGreaterThan(0);
// RepositoryInterceptor always not track entities
db.ChangeTracker.Entries().Count().ShouldBe(0);
});
}
[Fact]
public async Task RepositoryInterceptor_Test()
{
await AddSomePeopleAsync();
var repository = GetRequiredService<IRepository<Person, Guid>>();
await WithUnitOfWorkAsync(async () =>
{
var service = GetRequiredService<MyService>();
var db = await repository.GetDbContextAsync();
db.ChangeTracker.Entries().Count().ShouldBe(0);
var list = await service.GetPeoplesAsync();
list.Count.ShouldBeGreaterThan(0);
db.ChangeTracker.Entries().Count().ShouldBe(1); // Track one entity from GetPeopleAsync
});
await WithUnitOfWorkAsync(async () =>
{
var service = GetRequiredService<MyServiceEnableEntityChangeTracking>();
var db = await repository.GetDbContextAsync();
db.ChangeTracker.Entries().Count().ShouldBe(0);
var list = await service.GetPeoplesAsync();
list.Count.ShouldBeGreaterThan(0);
db.ChangeTracker.Entries().Count().ShouldBe(1); // Track one entity from GetPeoplesAsync
});
await WithUnitOfWorkAsync(async () =>
{
var service = GetRequiredService<MyServiceChangeTrackingByEntityChangeTrackingProvider>();
var db = await repository.GetDbContextAsync();
db.ChangeTracker.Entries().Count().ShouldBe(0);
var entityChangeTrackingProvider = GetRequiredService<IEntityChangeTrackingProvider>();
// Disable entity change tracking
using (entityChangeTrackingProvider.Change(false))
{
var list = await service.GetPeoplesAsync();
list.Count.ShouldBeGreaterThan(0);
db.ChangeTracker.Entries().Count().ShouldBe(0);
}
});
}
private async Task AddSomePeopleAsync()
{
var repository = GetRequiredService<IRepository<Person, Guid>>();
await repository.InsertAsync(new Person(Guid.NewGuid(), "people1", 18));
await repository.InsertAsync(new Person(Guid.NewGuid(), "people2", 19));
await repository.InsertAsync(new Person(Guid.NewGuid(), "people3", 20));
await repository.InsertAsync(new Person(Guid.NewGuid(), "people4", 21));
}
}
public class MyService : ITransientDependency
{
private readonly IRepository<Person, Guid> _repository;
public MyService(IRepository<Person, Guid> repository)
{
_repository = repository;
}
[DisableEntityChangeTracking]
public virtual async Task<List<Person>> GetPeoplesAsync()
{
await GetPeopleAsync();
return await _repository.GetListAsync();
}
[EnableEntityChangeTracking]
public virtual async Task<Person> GetPeopleAsync()
{
var p1 = await _repository.FindAsync(x => x.Name == "people1");
return p1;
}
}
public class MyReadOnlyService : MyService
{
public MyReadOnlyService(IReadOnlyRepository<Person, Guid> repository)
: base(repository.As<IRepository<Person, Guid>>())
{
}
}
[EnableEntityChangeTracking]
public class MyServiceEnableEntityChangeTracking : ITransientDependency
{
private readonly IRepository<Person, Guid> _repository;
public MyServiceEnableEntityChangeTracking(IRepository<Person, Guid> repository)
{
_repository = repository;
}
public virtual async Task<List<Person>> GetPeoplesAsync()
{
var p1 = await GetPeopleAsync();
var p2 = await _repository.FindAsync(x => x.Name == "people2");
return new List<Person> {p1, p2};
}
[DisableEntityChangeTracking]
public virtual async Task<Person> GetPeopleAsync()
{
var p1 = await _repository.FindAsync(x => x.Name == "people1");
return p1;
}
}
public class MyServiceChangeTrackingByEntityChangeTrackingProvider : ITransientDependency
{
private readonly IRepository<Person, Guid> _repository;
public MyServiceChangeTrackingByEntityChangeTrackingProvider(IRepository<Person, Guid> repository)
{
_repository = repository;
}
public virtual async Task<List<Person>> GetPeoplesAsync()
{
return await _repository.GetListAsync();
}
}

41
framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/Repositories/ReadOnlyRepository_Tests.cs

@ -2,13 +2,9 @@ using System;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using Shouldly;
using Volo.Abp.Data;
using Volo.Abp.Domain.Repositories;
using Volo.Abp.Domain.Repositories.EntityFrameworkCore;
using Volo.Abp.TestApp.Domain;
using Volo.Abp.TestApp.EntityFrameworkCore;
using Volo.Abp.TestApp.Testing;
using Volo.Abp.Uow;
using Xunit;
@ -55,23 +51,42 @@ public class ReadOnlyRepository_Tests : TestAppTestBase<AbpEntityFrameworkCoreTe
}
[Fact]
public async Task ReadOnlyRepository_Should_Throw_AbpRepositoryIsReadOnlyException_When_Write_Method_Call()
public async Task Repository_Should_Support_Tracking_Or_NoTracking()
{
var repository = GetRequiredService<IRepository<Person, Guid>>();
await WithUnitOfWorkAsync(async () =>
{
var repository = GetRequiredService<IRepository<Person, Guid>>();
await repository.ToEfCoreRepository().InsertAsync(new Person(Guid.NewGuid(), "test", 18));
var person = await repository.ToEfCoreRepository().FirstOrDefaultAsync();
person.ShouldNotBeNull();
await repository.InsertAsync(new Person(Guid.NewGuid(), "people1", 18));
await repository.InsertAsync(new Person(Guid.NewGuid(), "people2", 19));
await repository.InsertAsync(new Person(Guid.NewGuid(), "people3", 20));
await repository.InsertAsync(new Person(Guid.NewGuid(), "people4", 21));
});
await WithUnitOfWorkAsync(async () =>
{
await Assert.ThrowsAsync<AbpRepositoryIsReadOnlyException>(async () =>
var db = await repository.GetDbContextAsync();
db.ChangeTracker.Entries().Count().ShouldBe(0);
using (repository.DisableTracking())
{
var readonlyRepository = GetRequiredService<IReadOnlyRepository<Person, Guid>>();
await readonlyRepository.ToEfCoreRepository().As<EfCoreRepository<TestAppDbContext, Person, Guid>>().InsertAsync(new Person(Guid.NewGuid(), "test readonly", 18));
});
var p1 = await repository.FindAsync(x => x.Name == "people1");
p1.ShouldNotBeNull();
db.ChangeTracker.Entries().Count().ShouldBe(0);
}
var p2 = await repository.FindAsync(x => x.Name == "people2");
p2.ShouldNotBeNull();
db.ChangeTracker.Entries().Count().ShouldBe(1);
repository.DisableTracking();
var p3 = await repository.FindAsync(x => x.Name == "people3");
p3.ShouldNotBeNull();
db.ChangeTracker.Entries().Count().ShouldBe(1);
repository.EnableTracking();
var p4 = await repository.FindAsync(x => x.Name == "people4");
p4.ShouldNotBeNull();
db.ChangeTracker.Entries().Count().ShouldBe(2);
});
}

4
modules/blob-storing-database/src/Volo.Abp.BlobStoring.Database.Domain/Volo/Abp/BlobStoring/Database/DatabaseBlob.cs

@ -17,6 +17,10 @@ public class DatabaseBlob : AggregateRoot<Guid>, IMultiTenant
[DisableAuditing]
public virtual byte[] Content { get; protected set; }
protected DatabaseBlob()
{
}
public DatabaseBlob(Guid id, Guid containerId, [NotNull] string name, [NotNull] byte[] content, Guid? tenantId = null)
: base(id)
{

4
modules/blob-storing-database/src/Volo.Abp.BlobStoring.Database.Domain/Volo/Abp/BlobStoring/Database/DatabaseBlobContainer.cs

@ -11,6 +11,10 @@ public class DatabaseBlobContainer : AggregateRoot<Guid>, IMultiTenant
public virtual string Name { get; protected set; }
protected DatabaseBlobContainer()
{
}
public DatabaseBlobContainer(Guid id, [NotNull] string name, Guid? tenantId = null)
: base(id)
{

4
modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/Blogs/Blog.cs

@ -16,6 +16,10 @@ public class Blog : FullAuditedAggregateRoot<Guid>, IMultiTenant
public virtual Guid? TenantId { get; protected set; }
protected Blog()
{
}
protected internal Blog(
Guid id,
[NotNull] string name,

4
modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/Blogs/BlogFeature.cs

@ -13,6 +13,10 @@ public class BlogFeature : FullAuditedAggregateRoot<Guid>
public bool IsEnabled { get; protected internal set; }
protected BlogFeature()
{
}
public BlogFeature(Guid blogId, [NotNull] string featureName, bool isEnabled = true)
{
BlogId = blogId;

4
modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/Menus/MenuItem.cs

@ -41,6 +41,10 @@ public class MenuItem : AuditedAggregateRoot<Guid>, IMultiTenant
public Guid? TenantId { get; protected set; }
protected MenuItem()
{
}
public MenuItem(Guid id,
[NotNull] string displayName,
[NotNull] string url,

5
modules/identity/src/Volo.Abp.Identity.Application/Volo/Abp/Identity/IdentityUserAppService.cs

@ -173,7 +173,10 @@ public class IdentityUserAppService : IdentityAppServiceBase, IIdentityUserAppSe
(await UserManager.SetPhoneNumberAsync(user, input.PhoneNumber)).CheckErrors();
}
(await UserManager.SetLockoutEnabledAsync(user, input.LockoutEnabled)).CheckErrors();
if (user.Id != CurrentUser.Id)
{
(await UserManager.SetLockoutEnabledAsync(user, input.LockoutEnabled)).CheckErrors();
}
user.Name = input.Name;
user.Surname = input.Surname;

9
modules/identity/src/Volo.Abp.Identity.Blazor/Pages/Identity/UserManagement.razor

@ -129,9 +129,12 @@
<Field>
<Check TValue="bool" @bind-Checked="@NewEntity.IsActive">@L["DisplayName:IsActive"]</Check>
</Field>
<Field>
<Check TValue="bool" @bind-Checked="@NewEntity.LockoutEnabled">@L["DisplayName:LockoutEnabled"]</Check>
</Field>
@if (!IsEditCurrentUser)
{
<Field>
<Check TValue="bool" @bind-Checked="@NewEntity.LockoutEnabled">@L["DisplayName:LockoutEnabled"]</Check>
</Field>
}
<ExtensionProperties TEntityType="IdentityUserCreateDto" TResourceType="IdentityResource" Entity="@NewEntity" LH="@LH" ModalType="ExtensionPropertyModalType.CreateModal" />
</TabPanel>
<TabPanel Name="Roles">

3
modules/identity/src/Volo.Abp.Identity.Blazor/Pages/Identity/UserManagement.razor.cs

@ -41,6 +41,7 @@ public partial class UserManagement
private List<TableColumn> UserManagementTableColumns => TableColumns.Get<UserManagement>();
private TextRole _passwordTextRole = TextRole.Password;
public bool IsEditCurrentUser { get; set; }
public UserManagement()
{
@ -119,7 +120,7 @@ public partial class UserManagement
try
{
EditModalSelectedTab = DefaultSelectedTab;
IsEditCurrentUser = entity.Id == CurrentUser.Id;
var userRoleNames = (await AppService.GetRolesAsync(entity.Id)).Items.Select(r => r.Name).ToList();
EditUserRoles = Roles.Select(x => new AssignedRoleViewModel

6
modules/identity/src/Volo.Abp.Identity.Web/Pages/Identity/Users/EditModal.cshtml

@ -39,8 +39,10 @@
<abp-input asp-for="UserInfo.Email" />
<abp-input asp-for="UserInfo.PhoneNumber" />
<abp-input asp-for="UserInfo.IsActive" />
<abp-input asp-for="UserInfo.LockoutEnabled" />
@if (!Model.IsEditCurrentUser)
{
<abp-input asp-for="UserInfo.LockoutEnabled" />
}
@foreach (var propertyInfo in ObjectExtensionManager.Instance.GetProperties<EditModalModel.UserInfoViewModel>())
{
if (!propertyInfo.Name.EndsWith("_Text"))

5
modules/identity/src/Volo.Abp.Identity.Web/Pages/Identity/Users/EditModal.cshtml.cs

@ -24,6 +24,8 @@ public class EditModalModel : IdentityPageModel
protected IIdentityUserAppService IdentityUserAppService { get; }
public bool IsEditCurrentUser { get; set; }
public EditModalModel(IIdentityUserAppService identityUserAppService)
{
IdentityUserAppService = identityUserAppService;
@ -32,10 +34,9 @@ public class EditModalModel : IdentityPageModel
public virtual async Task<IActionResult> OnGetAsync(Guid id)
{
var user = await IdentityUserAppService.GetAsync(id);
UserInfo = ObjectMapper.Map<IdentityUserDto, UserInfoViewModel>(user);
Roles = ObjectMapper.Map<IReadOnlyList<IdentityRoleDto>, AssignedRoleViewModel[]>((await IdentityUserAppService.GetAssignableRolesAsync()).Items);
IsEditCurrentUser = CurrentUser.Id == id;
var userRoleNames = (await IdentityUserAppService.GetRolesAsync(UserInfo.Id)).Items.Select(r => r.Name).ToList();
foreach (var role in Roles)

1
npm/ng-packs/packages/theme-shared/src/lib/handlers/error.handler.ts

@ -22,7 +22,6 @@ export class ErrorHandler {
protected readonly routerErrorHandlerService = inject(RouterErrorHandlerService);
protected readonly httpErrorConfig = inject(HTTP_ERROR_CONFIG);
protected readonly customErrorHandlers = inject(CUSTOM_ERROR_HANDLERS);
protected readonly defaultHttpErrorHandler = (_, err: HttpErrorResponse) => throwError(() => err);
protected readonly httpErrorHandler =
inject(HTTP_ERROR_HANDLER, { optional: true }) || this.defaultHttpErrorHandler;

Loading…
Cancel
Save