I have following two entities:
public class FunctionalPosition : FullAuditedAggregateRoot<Guid>, IMultiTenant
and public class AccessRequest : FullAuditedEntity<Guid>, IMultiTenant
DomainService for FunctionalPosition
DomainService for AccessRequest
FunctionalPosition Table
AccessRequest Table
FunctionalPosition
and AccessRequest
are in the same microservice and DB, but there is wrong with AccessRequest
to set user and tenant.
Also I am checking by debugging both client and server has the current tenant/user information. Am I missing something?
I added a new microservice that named FileManagerService
and added database blob storing to it by using this command: abp add-module Volo.Abp.BlobStoring.Database
.
All looks fine but when I trying to save files, provider is using AdminstrationServiceDB
.
[Authorize(FileManagerServicePermissions.FileManager.Files.FilesDefault)]
public class FileAppService : ApplicationService, IFileAppService
{
private IBlobContainer _fileContainer;
private readonly IBlobContainerFactory _blobContainerFactory;
public FileAppService(IBlobContainerFactory blobContainerFactory)
{
_blobContainerFactory = blobContainerFactory;
}
public async Task<BlobDto> GetBlobAsync(GetBlobRequestDto input)
{
var blob = await _fileContainer.GetAllBytesAsync(input.Name);
return new BlobDto
{
Name = input.Name,
Content = blob
};
}
[Authorize(FileManagerServicePermissions.FileManager.Files.Create)]
[Authorize(FileManagerServicePermissions.FileManager.Files.Edit)]
public async Task SaveBlobAsync(CreateBlobInput input)
{
_fileContainer = _blobContainerFactory.Create("microservice-name");
await _fileContainer.SaveAsync(input.Name, input.Content, true);
}
How I configure blobContainer to use file-manager
connection string, instead of using administration-service
connection string?
And this is the file-manager-service
connection strings in appsettings.json
.
And DB
public class AccessRequestApprovedHandler : IDistributedEventHandler<AccessRequestApprovedEto>, ITransientDependency
{
private readonly IdentityUserManager _userManager;
private readonly ICustomIdentityRoleRepository _customIdentityRoleRepository;
private readonly ILogger<AccessRequestApprovedHandler> _logger;
public AccessRequestApprovedHandler(IdentityUserManager userManager, ICustomIdentityRoleRepository customIdentityRoleRepository, ILogger<AccessRequestApprovedHandler> logger)
{
_userManager = userManager;
_customIdentityRoleRepository = customIdentityRoleRepository;
_logger = logger;
}
[UnitOfWork]
public async Task HandleEventAsync(AccessRequestApprovedEto eventData)
{
try
{
var user = await _userManager.GetByIdAsync(eventData.UserId);
var roles = await _customIdentityRoleRepository.GetListByIdsAsync(eventData.RoleIds);
// Exception is thrown here.
var identityResult = await _userManager.SetRolesAsync(user, roles.Select(r => r.Name).ToArray());
if (!identityResult.Succeeded)
{
_logger.LogError($"Error occured while setting roles for user: {user.UserName}");
foreach (var error in identityResult.Errors)
{
_logger.LogError($"Error: {error.Code} - {error.Description}");
}
}
}
catch (Exception e)
{
Console.WriteLine(e);
throw;
}
}
}
Exception Message (even though I am using UnitOfWork
attribute) : A DbContext can only be created inside a unit of work!
at Volo.Abp.Uow.EntityFrameworkCore.UnitOfWorkDbContextProvider`1.GetDbContextAsync()
at Volo.Abp.Domain.Repositories.EntityFrameworkCore.EfCoreRepository`2.EnsureCollectionLoadedAsync[TProperty](TEntity entity, Expression`1 propertyExpression, CancellationToken cancellationToken)
at Volo.Abp.Domain.Repositories.RepositoryExtensions.EnsureCollectionLoadedAsync[TEntity,TKey,TProperty](IBasicRepository`2 repository, TEntity entity, Expression`1 propertyExpression, CancellationToken cancellationToken)
at Volo.Abp.Identity.IdentityUserStore.AddToRoleAsync(IdentityUser user, String normalizedRoleName, CancellationToken cancellationToken)
at Microsoft.AspNetCore.Identity.UserManager`1.AddToRolesAsync(TUser user, IEnumerable`1 roles)
at Castle.DynamicProxy.AsyncInterceptorBase.ProceedAsynchronous[TResult](IInvocation invocation, IInvocationProceedInfo proceedInfo)
at Volo.Abp.Castle.DynamicProxy.CastleAbpMethodInvocationAdapterWithReturnValue`1.ProceedAsync()
at Volo.Abp.Uow.UnitOfWorkInterceptor.InterceptAsync(IAbpMethodInvocation invocation)
at Volo.Abp.Castle.DynamicProxy.CastleAsyncAbpInterceptorAdapter`1.InterceptAsync[TResult](IInvocation invocation, IInvocationProceedInfo proceedInfo, Func`3 proceed)
at Volo.Abp.Identity.IdentityUserManager.SetRolesAsync(IdentityUser user, IEnumerable`1 roleNames)
at Castle.DynamicProxy.AsyncInterceptorBase.ProceedAsynchronous[TResult](IInvocation invocation, IInvocationProceedInfo proceedInfo)
at Volo.Abp.Castle.DynamicProxy.CastleAbpMethodInvocationAdapterWithReturnValue`1.ProceedAsync()
at Volo.Abp.Uow.UnitOfWorkInterceptor.InterceptAsync(IAbpMethodInvocation invocation)
at Volo.Abp.Castle.DynamicProxy.CastleAsyncAbpInterceptorAdapter`1.InterceptAsync[TResult](IInvocation invocation, IInvocationProceedInfo proceedInfo, Func`3 proceed)
at Daisy.IdentityService.EventHandlers.AccessRequests.AccessRequestApprovedHandler.HandleEventAsync(AccessRequestApprovedEto eventData) in C:\Users\aliriza\Documents\Projects\GitHub\Daisy\services\identity\src\Daisy.IdentityService.Application\EventHandlers\AccessRequests\AccessRequestApprovedHandler.cs:line 34
BTW, here is repo method implementation:
public async Task<List<IdentityRole>> GetListByIdsAsync(Guid[] ids, CancellationToken cancellationToken = default)
{
var query = await GetQueryableAsync();
query = query.Where(r => ids.Contains(r.Id)).OrderBy(x => x.Name);
return await query.ToListAsync(cancellationToken);
}
I think it is coming from here: UnitOfWorkDbContextProvider.cs. it seems it can not use the current unit of work.
IdentityUserManager
- SetRolesAsync
- AddToRolesAsync
- userRoleStore.AddToRoleAsync
- UserRepository.EnsureCollectionLoadedAsync
- GetDbContextAsync()
- _dbContextProvider.GetDbContextAsync()
I also try IUnitOfWorkEnable
interface or inherite from ApplicationService
but no luck.
It is only working when I manage uow manually. Following is working:
public async Task HandleEventAsync(AccessRequestApprovedEto eventData)
{
_logger.LogInformation("Event name: {@eventName}. User id: {@userId}. Role ids: {@roleIds}", AccessRequestConsts.EventNames.Approved, eventData.UserId, eventData.RoleIds);
using var uow = _unitOfWorkManager.Begin();
var user = await _userManager.GetByIdAsync(eventData.UserId);
var roles = await _customIdentityRoleRepository.GetListByIdsAsync(eventData.RoleIds);
var identityResult = await _userManager.SetRolesAsync(user, roles.Select(r => r.Name).ToArray());
if (!identityResult.Succeeded)
{
_logger.LogError($"Error occured while setting roles for user: {user.UserName}");
foreach (var error in identityResult.Errors)
{
_logger.LogError($"Error: {error.Code} - {error.Description}");
}
}
await uow.CompleteAsync();
}
But I cant understand why [UnitOfWork]
attribute is not working?
Identity API
I am customizing Identity module according to this document: Customizing the Application Modules
I extended IdentityRoles service/repository/contoller so I have a method _roleService.GetListByIds(Guid[] ids)
. Everything is ok, so far. The end-point is working like expected.
Identity Angular
Question: I am creating ng-library for my each microservice to generate proxies, separately (reference:Microservice Proxy Generation). To use my new method that is in IdentityRoleService, should I create an ng-library for identity and generate identity proxies in it? So, as I understand, I will use these proxies instead of using them from abp/ng.core
?
Any suggestion would be appreciated! Thanks...