Browse Source

Added ISupportsExplicitLoading feature and used while accessing IdentityUser.Roles.

pull/113/head
Halil İbrahim Kalkan 9 years ago
parent
commit
dce591f19c
  1. 23
      src/Volo.Abp.EntityFrameworkCore/Volo/Abp/Domain/Repositories/EntityFrameworkCore/EfCoreRepository.cs
  2. 2
      src/Volo.Abp.Identity.Application.Contracts/Volo/Abp/Identity/IIdentityUserAppService.cs
  3. 2
      src/Volo.Abp.Identity.Application.Contracts/Volo/Abp/Identity/IdentityUserCreateOrUpdateDtoBase.cs
  4. 10
      src/Volo.Abp.Identity.Application.Contracts/Volo/Abp/Identity/UpdateIdentityUserRolesDto.cs
  5. 10
      src/Volo.Abp.Identity.Application/Volo/Abp/Identity/IdentityUserAppService.cs
  6. 1
      src/Volo.Abp.Identity/Volo/Abp/Identity/IdentityUser.cs
  7. 8
      src/Volo.Abp.Identity/Volo/Abp/Identity/IdentityUserManager.cs
  8. 20
      src/Volo.Abp.Identity/Volo/Abp/Identity/IdentityUserStore.cs
  9. 25
      src/Volo.Abp/Volo/Abp/Domain/Repositories/ISupportsExplicitLoading.cs
  10. 68
      src/Volo.Abp/Volo/Abp/Domain/Repositories/RepositoryExtensions.cs
  11. 32
      src/Volo.Abp/Volo/Abp/DynamicProxy/ProxyHelper.cs
  12. 1
      test/Volo.Abp.Identity.Application.Tests/Volo/Abp/Identity/AbpIdentityTestDataBuilder.cs
  13. 37
      test/Volo.Abp.Identity.Application.Tests/Volo/Abp/Identity/IdentityUserAppService_Tests.cs

23
src/Volo.Abp.EntityFrameworkCore/Volo/Abp/Domain/Repositories/EntityFrameworkCore/EfCoreRepository.cs

@ -20,7 +20,10 @@ namespace Volo.Abp.Domain.Repositories.EntityFrameworkCore
}
}
public class EfCoreRepository<TDbContext, TEntity, TPrimaryKey> : QueryableRepositoryBase<TEntity, TPrimaryKey>, IEfCoreRepository<TEntity, TPrimaryKey>
public class EfCoreRepository<TDbContext, TEntity, TPrimaryKey> : QueryableRepositoryBase<TEntity, TPrimaryKey>,
IEfCoreRepository<TEntity, TPrimaryKey>,
ISupportsExplicitLoading<TEntity, TPrimaryKey>
where TDbContext : AbpDbContext<TDbContext>
where TEntity : class, IEntity<TPrimaryKey>
{
@ -117,5 +120,23 @@ namespace Volo.Abp.Domain.Repositories.EntityFrameworkCore
{
return GetQueryable().LongCountAsync(cancellationToken);
}
public virtual Task EnsureCollectionLoadedAsync<TProperty>(
TEntity entity,
Expression<Func<TEntity, IEnumerable<TProperty>>> propertyExpression,
CancellationToken cancellationToken)
where TProperty : class
{
return DbContext.Entry(entity).Collection(propertyExpression).LoadAsync(cancellationToken);
}
public virtual Task EnsurePropertyLoadedAsync<TProperty>(
TEntity entity,
Expression<Func<TEntity, TProperty>> propertyExpression,
CancellationToken cancellationToken)
where TProperty : class
{
return DbContext.Entry(entity).Reference(propertyExpression).LoadAsync(cancellationToken);
}
}
}

2
src/Volo.Abp.Identity.Application.Contracts/Volo/Abp/Identity/IIdentityUserAppService.cs

@ -8,5 +8,7 @@ namespace Volo.Abp.Identity
public interface IIdentityUserAppService : IAsyncCrudAppService<IdentityUserDto, Guid, PagedAndSortedResultRequestDto, IdentityUserCreateDto, IdentityUserUpdateDto>
{
Task<ListResultDto<IdentityRoleDto>> GetRolesAsync(Guid id);
Task UpdateRolesAsync(Guid id, UpdateIdentityUserRolesDto input);
}
}

2
src/Volo.Abp.Identity.Application.Contracts/Volo/Abp/Identity/IdentityUserCreateOrUpdateDto.cs → src/Volo.Abp.Identity.Application.Contracts/Volo/Abp/Identity/IdentityUserCreateOrUpdateDtoBase.cs

@ -15,6 +15,6 @@ namespace Volo.Abp.Identity
public bool LockoutEnabled { get; set; } //TODO: Optional?
[CanBeNull]
public string[] Roles { get; set; }
public string[] RoleNames { get; set; }
}
}

10
src/Volo.Abp.Identity.Application.Contracts/Volo/Abp/Identity/UpdateIdentityUserRolesDto.cs

@ -0,0 +1,10 @@
using System.ComponentModel.DataAnnotations;
namespace Volo.Abp.Identity
{
public class UpdateIdentityUserRolesDto
{
[Required]
public string[] RoleNames { get; set; }
}
}

10
src/Volo.Abp.Identity.Application/Volo/Abp/Identity/IdentityUserAppService.cs

@ -75,6 +75,12 @@ namespace Volo.Abp.Identity
);
}
public async Task UpdateRolesAsync(Guid id, UpdateIdentityUserRolesDto input)
{
var user = await _userManager.GetByIdAsync(id);
await _userManager.SetRolesAsync(user, input.RoleNames);
}
private async Task UpdateUserByInput(IdentityUser user, IdentityUserCreateOrUpdateDtoBase input)
{
await _userManager.SetEmailAsync(user, input.Email);
@ -82,9 +88,9 @@ namespace Volo.Abp.Identity
await _userManager.SetTwoFactorEnabledAsync(user, input.TwoFactorEnabled);
await _userManager.SetLockoutEnabledAsync(user, input.LockoutEnabled);
if (input.Roles != null)
if (input.RoleNames != null)
{
await _userManager.SetRolesAsync(user, input.Roles);
await _userManager.SetRolesAsync(user, input.RoleNames);
}
}
}

1
src/Volo.Abp.Identity/Volo/Abp/Identity/IdentityUser.cs

@ -131,6 +131,7 @@ namespace Volo.Abp.Identity
UserName = userName;
NormalizedUserName = userName.ToUpperInvariant();
ConcurrencyStamp = Guid.NewGuid().ToString();
SecurityStamp = Guid.NewGuid().ToString();
Roles = new Collection<IdentityUserRole>();
Claims = new Collection<IdentityUserClaim>();

8
src/Volo.Abp.Identity/Volo/Abp/Identity/IdentityUserManager.cs

@ -47,15 +47,15 @@ namespace Volo.Abp.Identity
return user;
}
public async Task SetRolesAsync([NotNull] IdentityUser user, [NotNull] string[] roleNames)
public async Task SetRolesAsync([NotNull] IdentityUser user, [NotNull] IEnumerable<string> roleNames)
{
Check.NotNull(user, nameof(user));
Check.NotNull(roleNames, nameof(roleNames));
var currentRoleNames = await GetRolesAsync(user);
await RemoveFromRolesAsync(user, currentRoleNames.Except(roleNames));
await AddToRolesAsync(user, roleNames.Except(currentRoleNames));
await RemoveFromRolesAsync(user, currentRoleNames.Except(roleNames).Distinct());
await AddToRolesAsync(user, roleNames.Except(currentRoleNames).Distinct());
}
}
}

20
src/Volo.Abp.Identity/Volo/Abp/Identity/IdentityUserStore.cs

@ -9,6 +9,7 @@ using JetBrains.Annotations;
using Microsoft.AspNetCore.Identity;
using Microsoft.Extensions.Logging;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Domain.Repositories;
using Volo.Abp.Guids;
using Volo.Abp.Uow;
@ -332,6 +333,8 @@ namespace Volo.Abp.Identity
throw new InvalidOperationException(string.Format(CultureInfo.CurrentCulture, "Role {0} does not exist!", normalizedRoleName));
}
await _userRepository.EnsureCollectionLoadedAsync(user, u => u.Roles, cancellationToken);
user.AddRole(_guidGenerator, role.Id);
}
@ -359,6 +362,8 @@ namespace Volo.Abp.Identity
return;
}
await _userRepository.EnsureCollectionLoadedAsync(user, u => u.Roles, cancellationToken);
user.RemoveRole(role.Id);
}
@ -402,15 +407,9 @@ namespace Volo.Abp.Identity
return false;
}
return user.IsInRole(role.Id);
}
await _userRepository.EnsureCollectionLoadedAsync(user, u => u.Roles, cancellationToken);
/// <summary>
/// Dispose the store
/// </summary>
public void Dispose()
{
return user.IsInRole(role.Id);
}
/// <summary>
@ -1037,5 +1036,10 @@ namespace Volo.Abp.Identity
return Task.FromResult(user.FindToken(loginProvider, name)?.Value);
}
public void Dispose()
{
}
}
}

25
src/Volo.Abp/Volo/Abp/Domain/Repositories/ISupportsExplicitLoading.cs

@ -0,0 +1,25 @@
using System;
using System.Collections.Generic;
using System.Linq.Expressions;
using System.Threading;
using System.Threading.Tasks;
using Volo.Abp.Domain.Entities;
namespace Volo.Abp.Domain.Repositories
{
public interface ISupportsExplicitLoading<TEntity, TPrimaryKey>
where TEntity : class, IEntity<TPrimaryKey>
{
Task EnsureCollectionLoadedAsync<TProperty>(
TEntity entity,
Expression<Func<TEntity, IEnumerable<TProperty>>> propertyExpression,
CancellationToken cancellationToken)
where TProperty : class;
Task EnsurePropertyLoadedAsync<TProperty>(
TEntity entity,
Expression<Func<TEntity, TProperty>> propertyExpression,
CancellationToken cancellationToken)
where TProperty : class;
}
}

68
src/Volo.Abp/Volo/Abp/Domain/Repositories/RepositoryExtensions.cs

@ -0,0 +1,68 @@
using System;
using System.Collections.Generic;
using System.Linq.Expressions;
using System.Threading;
using System.Threading.Tasks;
using Volo.Abp.Domain.Entities;
using Volo.Abp.DynamicProxy;
using Volo.Abp.Threading;
namespace Volo.Abp.Domain.Repositories
{
public static class RepositoryExtensions
{
public static async Task EnsureCollectionLoadedAsync<TEntity, TPrimaryKey, TProperty>(
this IRepository<TEntity, TPrimaryKey> repository,
TEntity entity,
Expression<Func<TEntity, IEnumerable<TProperty>>> propertyExpression,
CancellationToken cancellationToken = default(CancellationToken)
)
where TEntity : class, IEntity<TPrimaryKey>
where TProperty : class
{
var repo = ProxyHelper.UnProxy(repository) as ISupportsExplicitLoading<TEntity, TPrimaryKey>;
if (repo != null)
{
await repo.EnsureCollectionLoadedAsync(entity, propertyExpression, cancellationToken);
}
}
public static void EnsureCollectionLoaded<TEntity, TPrimaryKey, TProperty>(
this IRepository<TEntity, TPrimaryKey> repository,
TEntity entity,
Expression<Func<TEntity, IEnumerable<TProperty>>> propertyExpression
)
where TEntity : class, IEntity<TPrimaryKey>
where TProperty : class
{
AsyncHelper.RunSync(() => repository.EnsureCollectionLoadedAsync(entity, propertyExpression));
}
public static async Task EnsurePropertyLoadedAsync<TEntity, TPrimaryKey, TProperty>(
this IRepository<TEntity, TPrimaryKey> repository,
TEntity entity,
Expression<Func<TEntity, TProperty>> propertyExpression,
CancellationToken cancellationToken = default(CancellationToken)
)
where TEntity : class, IEntity<TPrimaryKey>
where TProperty : class
{
var repo = ProxyHelper.UnProxy(repository) as ISupportsExplicitLoading<TEntity, TPrimaryKey>;
if (repo != null)
{
await repo.EnsurePropertyLoadedAsync(entity, propertyExpression, cancellationToken);
}
}
public static void EnsurePropertyLoaded<TEntity, TPrimaryKey, TProperty>(
this IRepository<TEntity, TPrimaryKey> repository,
TEntity entity,
Expression<Func<TEntity, TProperty>> propertyExpression
)
where TEntity : class, IEntity<TPrimaryKey>
where TProperty : class
{
AsyncHelper.RunSync(() => repository.EnsurePropertyLoadedAsync(entity, propertyExpression));
}
}
}

32
src/Volo.Abp/Volo/Abp/DynamicProxy/ProxyHelper.cs

@ -0,0 +1,32 @@
using System.Linq;
using System.Reflection;
namespace Volo.Abp.DynamicProxy
{
public static class ProxyHelper
{
/// <summary>
/// Returns dynamic proxy target object if this is a proxied object, otherwise returns the given object.
/// </summary>
public static object UnProxy(object obj)
{
//TODO: This code depends on Castle, so we should find a better way.
if (obj.GetType().Namespace != "Castle.Proxies")
{
return obj;
}
var targetField = obj.GetType().GetTypeInfo()
.GetFields()
.FirstOrDefault(f => f.Name == "__target");
if (targetField == null)
{
return obj;
}
return targetField.GetValue(obj);
}
}
}

1
test/Volo.Abp.Identity.Application.Tests/Volo/Abp/Identity/AbpIdentityTestDataBuilder.cs

@ -44,6 +44,7 @@ namespace Volo.Abp.Identity
{
var john = new IdentityUser(_guidGenerator.Create(), "john.nash");
john.Roles.Add(new IdentityUserRole(_guidGenerator.Create(), john.Id, _moderator.Id));
john.Roles.Add(new IdentityUserRole(_guidGenerator.Create(), john.Id, _supporterRole.Id));
_userRepository.Insert(john);
}
}

37
test/Volo.Abp.Identity.Application.Tests/Volo/Abp/Identity/IdentityUserAppService_Tests.cs

@ -4,7 +4,6 @@ using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
using Shouldly;
using Volo.Abp.Application.Dtos;
using Volo.Abp.Domain.Repositories;
using Xunit;
namespace Volo.Abp.Identity
@ -12,12 +11,12 @@ namespace Volo.Abp.Identity
public class IdentityUserAppService_Tests : AbpIdentityApplicationTestBase
{
private readonly IIdentityUserAppService _identityUserAppService;
private readonly IRepository<IdentityUser> _userRepository;
private readonly IIdentityUserRepository _userRepository;
public IdentityUserAppService_Tests()
{
_identityUserAppService = ServiceProvider.GetRequiredService<IIdentityUserAppService>();
_userRepository = ServiceProvider.GetRequiredService<IRepository<IdentityUser>>();
_userRepository = ServiceProvider.GetRequiredService<IIdentityUserRepository>();
}
[Fact]
@ -65,7 +64,7 @@ namespace Volo.Abp.Identity
LockoutEnabled = true,
PhoneNumber = CreateRandomPhoneNumber(),
Password = "123qwe",
Roles = new[] {"moderator"}
RoleNames = new[] { "moderator" }
};
//Act
@ -153,8 +152,34 @@ namespace Volo.Abp.Identity
//Assert
result.Items.Count.ShouldBe(1);
result.Items[0].Name.ShouldBe("moderator");
result.Items.Count.ShouldBe(2);
result.Items.ShouldContain(r => r.Name == "moderator");
result.Items.ShouldContain(r => r.Name == "supporter");
}
[Fact]
public async Task UpdateRolesAsync()
{
//Arrange
var johnNash = await GetUserAsync("john.nash");
//Act
await _identityUserAppService.UpdateRolesAsync(
johnNash.Id,
new UpdateIdentityUserRolesDto
{
RoleNames = new[] {"moderator", "admin"}
}
);
//Assert
var roleNames = await _userRepository.GetRoleNamesAsync(johnNash.Id);
roleNames.Count.ShouldBe(2);
roleNames.ShouldContain("admin");
roleNames.ShouldContain("moderator");
}
private async Task<IdentityUser> GetUserAsync(string userName)

Loading…
Cancel
Save