[maliming] said: hi
The
ApplicationConfigurationDtoCacheAbsoluteExpirationofAbpAspNetCoreMvcClientCacheOptionsis 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.
[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
ImportUsersFromFileAsyncmethod ofIdentityUserAppServiceto 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?
context.Services.AddControllers(options => { options.Conventions.Insert(0, new GlobalRouteReplaceConvention("api", "auth/api")); });
I don't think replacing the api controller convention will work in my case.
It's the actual url used to make the change-password request that is not accounting for the path base. I did try to change the conventions based on your response and the URL used internally is still the same as before.
Where is that URL taken from and how can I override it to include my base path?
Hey, thanks for the response.
Unless i'm mistaken, the auth server requests targeting the /api/account/my-profile within the Account/Manage page does not go through the web gateway.
Locally, when running everything as default and without any custom base path for my auth server, I do see all /api/account/my-profile requests reaching the auth server directly without going through the web gateway. I can repro the same behavior when starting from a new microservice template.
Repro steps:
Hi,
Thanks to the information you provided, I was able to reproduce the problem. The problem does not occur in the Application Layered template, but in the Microservice solution, so I could not reproduce it the first time. I will create an internal issue, and we will keep you informed about the progress. Thank you for your patience.
Thank you,
I'll wait for an issue link to track the progress
Thanks for the response.
Unfortunately it seems I can reproduce the issue even on a new microservice solution.
I'm on version 9.1
Steps to reproduce on my side:
 var currentTenant = context.ServiceProvider.GetRequiredService<ICurrentTenant>();
 Console.WriteLine($"Running on the browser: {OperatingSystem.IsBrowser()} Current tenant is {currentTenant.Id}: {currentTenant.Name}");
 
                                