Hello I did create a new boilerplate and found out that it was working in the boilerplate.
I found out that the redis cache was not configured properly in my custom service and now it's working fine
Thanks for the help !!
Hello,
Not sure if I was clear enough but I already tried that with no success.
Also, I don't see any relevance to set that configuration in the blazor server cause like I said I want the updated feature value in my backend microservice and not the ** front-end blazor. **
thanks :)
[maliming] said: Hi
You can configure it in blazor web app and blazor web app client projects.
Because blazor web app has blazor server and wasm projects.
Yes,
but the AbpAspNetCoreMvcClientCacheOptions
comes from the Volo.Abp.AspNetCore.Mvc.Client
namespace wich can't work in the blazor client (WASM). I get ;
There was no runtime pack for Microsoft.AspNetCore.App available for the specified RuntimeIdentifier 'browser-wasm'.
Is there something I'm missing? Also, shouldn't this absolute expiration configuration be placed in my microservice project, since that's where I want to consume the feature value—not in the front-end?
Additionally, I tried setting the tenant feature value to a different value again and waited for about 30 minutes, but the updated value still wasn't fetched by my service.
Thanks.
[maliming] said: hi
The
ApplicationConfigurationDtoCacheAbsoluteExpiration
ofAbpAspNetCoreMvcClientCacheOptions
is five minutes by default.Can you try to change it to 10 seconds to test again?
Thanks.
Thanks for the response.
Where should I configure those options?
I have a blazor web app for the front-end.
I tried to;
Configure<AbpAspNetCoreMvcClientCacheOptions>(options =>
{
options.ApplicationConfigurationDtoCacheAbsoluteExpiration = TimeSpan.FromSeconds(5);
});
In the blazor server; the feature value in my microservice is still not updated after 5 seconds In the microservice that check the feature; same thing
Here's the simple snippet i'm using as an example in one of my service to condition on the feature value :
public async Task<PagedResultDto<ResultDto>> GetResultsAsync(Guid id, GetResultsDto input)
{
// not updated based on the value set in the feature management from the host UI
var isNewFlowEnabled = await featureChecker.IsEnabledAsync(AdministrationFeatures.NewFlow);
if (isNewFlowEnabled)
{
return await GetNewResultsAsync(input);
}
else
{
return await GetLegacyResultsAsync(input);
}
}
I still need someone to provide a way to have the cache invalidated when updating features in the host so that I can confidently rely on the Feature Management module to configure feature flags for my application and deterministically know when those updates will be reflected in the application.
Hello,
I have a blazor web app solution based on the microservice template.
I'm trying to check if a feature is enabled or not within one of my custom microservice but the feature value does not get updated when I change the value in the features modal for a particular tenant i'm using. Is there something i'm missing? I want to have the feature value updated directly when check if the feature is enabled or not in my microservice.
My solution setup;
Is there some caching that does not get invalidate when updaing the feature value?
I tried waiting a long period of time and the feature value does not get updated I tried logout/login and the feature value is still the same.
Thanks
[maliming] said: hi
You can try to set command timeout to 60 seconds or more
public override void ConfigureServices(ServiceConfigurationContext context) { //... Configure<AbpDbContextOptions>(options => { options.UseSqlServer(x => x.CommandTimeout(60)); }); //... }
Hello,
I tried to set the command timeout to 2 minutes and I face the same issue regardless after a minute. After a minute the export-as-csv
call fails and returns a 401 Unauthorized. Here's the stack trace I get:
[14:51:15 INF] Executing action method Volo.Abp.Identity.IdentityUserController.GetListAsCsvFileAsync (Volo.Abp.Identity.Pro.HttpApi) - Validation state: Valid
[14:51:15 WRN] ---------- RemoteServiceErrorInfo ----------
{
"code": null,
"message": "An internal error occurred during your request!",
"details": null,
"data": null,
"validationErrors": null
}
[14:51:15 WRN] Invalid download token: 1d7c6dcbc0854168a1c9abc4a2323f23
Volo.Abp.Authorization.AbpAuthorizationException: Invalid download token: 1d7c6dcbc0854168a1c9abc4a2323f23
at Volo.Abp.Identity.IdentityUserAppService.CheckDownloadTokenAsync(String token, Boolean isInvalidUsersToken)
at Volo.Abp.Identity.IdentityUserAppService.GetExportUsersAsync(GetIdentityUserListAsFileInput input)
at Volo.Abp.Identity.IdentityUserAppService.GetListAsCsvFileAsync(GetIdentityUserListAsFileInput input)
at Castle.DynamicProxy.AsyncInterceptorBase.ProceedAsynchronous[TResult](IInvocation invocation, IInvocationProceedInfo proceedInfo)
at Volo.Abp.Castle.DynamicProxy.CastleAbpMethodInvocationAdapterWithReturnValue`1.ProceedAsync()
at Volo.Abp.GlobalFeatures.GlobalFeatureInterceptor.InterceptAsync(IAbpMethodInvocation invocation)
at Volo.Abp.Castle.DynamicProxy.CastleAsyncAbpInterceptorAdapter`1.InterceptAsync[TResult](IInvocation invocation, IInvocationProceedInfo proceedInfo, Func`3 proceed)
at Castle.DynamicProxy.AsyncInterceptorBase.ProceedAsynchronous[TResult](IInvocation invocation, IInvocationProceedInfo proceedInfo)
at Volo.Abp.Castle.DynamicProxy.CastleAbpMethodInvocationAdapterWithReturnValue`1.ProceedAsync()
at Volo.Abp.Authorization.AuthorizationInterceptor.InterceptAsync(IAbpMethodInvocation invocation)
at Volo.Abp.Castle.DynamicProxy.CastleAsyncAbpInterceptorAdapter`1.InterceptAsync[TResult](IInvocation invocation, IInvocationProceedInfo proceedInfo, Func`3 proceed)
at Castle.DynamicProxy.AsyncInterceptorBase.ProceedAsynchronous[TResult](IInvocation invocation, IInvocationProceedInfo proceedInfo)
at Volo.Abp.Castle.DynamicProxy.CastleAbpMethodInvocationAdapterWithReturnValue`1.ProceedAsync()
at Volo.Abp.Auditing.AuditingInterceptor.ProceedByLoggingAsync(IAbpMethodInvocation invocation, AbpAuditingOptions options, IAuditingHelper auditingHelper, IAuditLogScope auditLogScope)
at Volo.Abp.Auditing.AuditingInterceptor.InterceptAsync(IAbpMethodInvocation invocation)
at Volo.Abp.Castle.DynamicProxy.CastleAsyncAbpInterceptorAdapter`1.InterceptAsync[TResult](IInvocation invocation, IInvocationProceedInfo proceedInfo, Func`3 proceed)
at Castle.DynamicProxy.AsyncInterceptorBase.ProceedAsynchronous[TResult](IInvocation invocation, IInvocationProceedInfo proceedInfo)
at Volo.Abp.Castle.DynamicProxy.CastleAbpMethodInvocationAdapterWithReturnValue`1.ProceedAsync()
at Volo.Abp.Validation.ValidationInterceptor.InterceptAsync(IAbpMethodInvocation invocation)
at Volo.Abp.Castle.DynamicProxy.CastleAsyncAbpInterceptorAdapter`1.InterceptAsync[TResult](IInvocation invocation, IInvocationProceedInfo proceedInfo, Func`3 proceed)
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 lambda_method4261(Closure, Object)
at Microsoft.AspNetCore.Mvc.Infrastructure.ActionMethodExecutor.AwaitableObjectResultExecutor.Execute(ActionContext actionContext, IActionResultTypeMapper mapper, ObjectMethodExecutor executor, Object controller, Object[] arguments)
at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.<InvokeActionMethodAsync>g__Logged|12_1(ControllerActionInvoker invoker)
at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.<InvokeNextActionFilterAsync>g__Awaited|10_0(ControllerActionInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Rethrow(ActionExecutedContextSealed context)
at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted)
at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.<InvokeInnerFilterAsync>g__Awaited|13_0(ControllerActionInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeNextExceptionFilterAsync>g__Awaited|26_0(ResourceInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
[14:51:15 WRN] Code:
[14:51:15 INF] AuthenticationScheme: Bearer was challenged.
[14:51:15 INF] Executed action Volo.Abp.Identity.IdentityUserController.GetListAsCsvFileAsync (Volo.Abp.Identity.Pro.HttpApi) in 68.2107ms
[14:51:15 INF] Executed endpoint 'Volo.Abp.Identity.IdentityUserController.GetListAsCsvFileAsync (Volo.Abp.Identity.Pro.HttpApi)'
[14:51:15 INF] AUDIT LOG: [401: GET ] /api/identity/users/export-as-csv
hi
Can you try to override the
ImportUsersFromFileAsync
method ofIdentityUserAppService
to support batch import?We will change it in the next version.
Thanks.
[Authorize(IdentityPermissions.Users.Import)] public virtual async Task<ImportUsersFromFileOutput> ImportUsersFromFileAsync(ImportUsersFromFileInputWithStream input) { await IdentityOptions.SetAsync(); var stream = new MemoryStream(); await input.File.GetStream().CopyToAsync(stream); var invalidUsers = new List<InvalidImportUsersFromFileDto>(); List<InvalidImportUsersFromFileDto> waitingImportUsers; try { IConfiguration configuration = null; if (input.FileType == ImportUsersFromFileType.Csv) { configuration = new CsvConfiguration { Seperator = ';' }; } waitingImportUsers = (await stream.QueryAsync<InvalidImportUsersFromFileDto>(excelType: input.FileType == ImportUsersFromFileType.Excel ? ExcelType.XLSX : ExcelType.CSV, configuration: configuration)).ToList(); } catch (Exception) { throw new BusinessException(IdentityProErrorCodes.InvalidImportFileFormat); } if (!waitingImportUsers.Any()) { throw new BusinessException(IdentityProErrorCodes.NoUserFoundInFile); } var resultDto = new ImportUsersFromFileOutput { AllCount = waitingImportUsers.Count }; const int batchSize = 3; var totalUsers = waitingImportUsers.Count; var batchCount = (int)Math.Ceiling((double)totalUsers / batchSize); for (var batchIndex = 0; batchIndex < batchCount; batchIndex++) { var currentBatch = waitingImportUsers .Skip(batchIndex * batchSize) .Take(batchSize) .ToList(); var (invalidBatchUsers, successBatchUsers) = await ImportUsersAsync(currentBatch); invalidUsers.AddRange(invalidBatchUsers); if (invalidBatchUsers.Any() && successBatchUsers.Any()) { var (invalidBatchUsers2, successBatchUsers2) = await ImportUsersAsync(successBatchUsers); if (invalidBatchUsers2.Any()) { invalidUsers.AddRange(invalidBatchUsers2); invalidUsers.AddRange(successBatchUsers2); } } } if (invalidUsers.Any()) { var token = Guid.NewGuid().ToString("N"); await ImportInvalidUsersCache.SetAsync( token, new ImportInvalidUsersCacheItem { Token = token, InvalidUsers = invalidUsers, FileType = input.FileType }, new DistributedCacheEntryOptions { AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(1) }); resultDto.InvalidUsersDownloadToken = token; } resultDto.SucceededCount = resultDto.AllCount - invalidUsers.Count; resultDto.FailedCount = invalidUsers.Count; return resultDto; } protected virtual async Task<(List<InvalidImportUsersFromFileDto>, List<InvalidImportUsersFromFileDto>)> ImportUsersAsync(List<InvalidImportUsersFromFileDto> users) { var hasException = false; var invalidUsers = new List<InvalidImportUsersFromFileDto>(); var successUsers = new List<InvalidImportUsersFromFileDto>(); using (var uow = UnitOfWorkManager.Begin(requiresNew: true, isTransactional: true)) { foreach (var waitingImportUser in users) { try { var user = new IdentityUser( GuidGenerator.Create(), waitingImportUser.UserName, waitingImportUser.EmailAddress, CurrentTenant.Id ) { Surname = waitingImportUser.Surname, Name = waitingImportUser.Name }; if (!waitingImportUser.PhoneNumber.IsNullOrWhiteSpace()) { user.SetPhoneNumber(waitingImportUser.PhoneNumber, false); } if (!waitingImportUser.Password.IsNullOrWhiteSpace()) { (await UserManager.CreateAsync(user, waitingImportUser.Password)).CheckErrors(); } else { (await UserManager.CreateAsync(user)).CheckErrors(); } if (!waitingImportUser.AssignedRoleNames.IsNullOrWhiteSpace()) { (await UserManager.SetRolesAsync(user, waitingImportUser.AssignedRoleNames.Split(new[] { ",", ";" }, StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries))).CheckErrors(); } if (!waitingImportUser.AssignedOrganizationUnitNames.IsNullOrWhiteSpace()) { var ouNames = waitingImportUser.AssignedOrganizationUnitNames.Split(new[] { ",", ";" }, StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries).ToArray(); var ous = await OrganizationUnitRepository.GetListByDisplayNamesAsync(ouNames); if (ous.Any()) { await UserManager.SetOrganizationUnitsAsync(user, ous.Select(x => x.Id).ToArray()); } } successUsers.Add(waitingImportUser); } catch (Exception e) { hasException = true; waitingImportUser.ErrorReason = e is UserFriendlyException ? e.Message : e.ToString(); invalidUsers.Add(waitingImportUser); Logger.LogWarning(e, $"Import user failed: {waitingImportUser}"); } } await (hasException ? uow.RollbackAsync() : uow.CompleteAsync()); } return (invalidUsers, successUsers); }
Thanks for the update. I'll try that.
Any workaround for the Export as well?
Hello,
We're using the microservice template with a blazor web app for our application. All are running with ABP v9.1.0
When trying to import a **small ** users list in the user management page of the administration section it works fine but when trying to import (or even export) a "large" list of users (5000+) we get timeouts.
5000 users is not a lot and we expect the framework to be able to import them without any issues.
Is there any known limitations in terms of how many users should we import at a time?
Thanks