Hi,
I setup Event Outbox as below: EntityFrameworkCoreModule
[ConnectionStringName(JobManagementServiceDbProperties.ConnectionStringName)]
public class JobManagementServiceDbContext : AbpDbContext<JobManagementServiceDbContext>,
IHasEventInbox,
IHasEventOutbox
{
public JobManagementServiceDbContext(DbContextOptions<JobManagementServiceDbContext> options)
: base(options)
{
}
public DbSet<IncomingEventRecord> IncomingEvents { get; set; }
public DbSet<OutgoingEventRecord> OutgoingEvents { get; set; }
protected override void OnModelCreating(ModelBuilder builder)
{
base.OnModelCreating(builder);
builder.ConfigureEventInbox();
builder.ConfigureEventOutbox();
builder.ConfigureJobManagementService();
}
}
HostModule
[DependsOn(
typeof(AbpAspNetCoreSerilogModule),
typeof(AbpSwashbuckleModule),
typeof(AbpBackgroundJobsRabbitMqModule),
typeof(AbpAspNetCoreMultiTenancyModule),
typeof(AbpDistributedLockingModule),
typeof(AbpEventBusRabbitMqModule),
typeof(AbpCachingStackExchangeRedisModule),
private void ConfigureDistributedEventBus()
{
Configure<AbpDistributedEventBusOptions>(options =>
{
options.Inboxes.Configure(config =>
{
config.UseDbContext<JobManagementServiceDbContext>();
});
options.Outboxes.Configure(config =>
{
//config.IsSendingEnabled = false;
//config.Selector = type => type == typeof(JobOperationEto) || type == typeof(JobConfigurationChangedEto);
config.UseDbContext<JobManagementServiceDbContext>();
});
});
}
I published event as below:
[Authorize(JobManagementServicePermissions.JobConfigurations.Default)]
public class JobConfigurationAppService :
CrudAppService<JobConfiguration, JobConfigurationDto, Guid, PagedAndSortedResultRequestDto, CreateUpdateJobConfigurationDto>,
IJobConfigurationAppService
{
private readonly IDistributedEventBus _distributedEventBus;
private readonly IUnitOfWorkManager _unitOfWorkManager;
public JobConfigurationAppService(
IRepository<JobConfiguration, Guid> repository,
IDistributedEventBus distributedEventBus,
IUnitOfWorkManager unitOfWorkManager)
: base(repository)
{
_distributedEventBus = distributedEventBus;
_unitOfWorkManager = unitOfWorkManager;
}
[Authorize(JobManagementServicePermissions.JobConfigurations.Create)]
public override async Task<JobConfigurationDto> CreateAsync(CreateUpdateJobConfigurationDto input)
{
var result = await base.CreateAsync(input);
await _distributedEventBus.PublishAsync(new JobConfigurationChangedEto
{
JobName = input.JobName,
JobGroup = input.JobGroup,
TenantId = CurrentTenant.Id,
IsActive = input.IsActive,
Description = input.Description,
JobDataJson = input.JobDataJson,
CronExpression = input.CronExpression,
JobType = input.JobType
});
return result;
}
[Authorize(JobManagementServicePermissions.JobConfigurations.Edit)]
public override async Task<JobConfigurationDto> UpdateAsync(Guid id, CreateUpdateJobConfigurationDto input)
{
var result = await base.UpdateAsync(id, input);
await _distributedEventBus.PublishAsync(new JobConfigurationChangedEto
{
JobName = input.JobName,
JobGroup = input.JobGroup,
TenantId = CurrentTenant.Id,
IsActive = input.IsActive,
Description = input.Description,
JobDataJson = input.JobDataJson,
CronExpression = input.CronExpression,
JobType = input.JobType
});
return result;
}
[Authorize(JobManagementServicePermissions.JobConfigurations.Delete)]
public override async Task DeleteAsync(Guid id)
{
var job = await Repository.GetAsync(id);
await base.DeleteAsync(id);
await _distributedEventBus.PublishAsync(new JobConfigurationChangedEto
{
JobName = job.JobName,
JobGroup = job.JobGroup,
TenantId = job.TenantId,
IsActive = false, // Worker sees this flag and will remove Job from Quartz
Description = job.Description,
JobDataJson = job.JobDataJson,
CronExpression = job.CronExpression,
JobType = job.JobType
});
}
[Authorize(JobManagementServicePermissions.JobConfigurations.Trigger)]
public async Task TriggerNowAsync(Guid id)
{
var job = await Repository.GetAsync(id);
await _distributedEventBus.PublishAsync(new JobOperationEto
{
JobName = job.JobName,
JobGroup = job.JobGroup,
TenantId = job.TenantId,
ActionType = JobActionType.TriggerNow
}, useOutbox: false);
}
[Authorize(JobManagementServicePermissions.JobConfigurations.Pause)]
public async Task PauseAsync(Guid id)
{
var job = await Repository.GetAsync(id);
job.IsActive = false;
await Repository.UpdateAsync(job);
await _distributedEventBus.PublishAsync(new JobOperationEto
{
JobName = job.JobName,
JobGroup = job.JobGroup,
TenantId = job.TenantId,
ActionType = JobActionType.Pause
});
}
[Authorize(JobManagementServicePermissions.JobConfigurations.Resume)]
public async Task ResumeAsync(Guid id)
{
var job = await Repository.GetAsync(id);
job.IsActive = true;
await Repository.UpdateAsync(job);
await _distributedEventBus.PublishAsync(new JobOperationEto
{
JobName = job.JobName,
JobGroup = job.JobGroup,
TenantId = job.TenantId,
ActionType = JobActionType.Resume
});
}
}
Only TriggerNowAsync method with useOutbox: false work fine, other methods can not publish events. I tried to set config.IsSendingEnabled = false; in HostModule but not found any record in AbpEventOutbox. Please help.
I have a module named BaseComponent with following AppService:
public interface IStaticDataTypeAppService : IApplicationService
{
Task<LoadResult> GetListAsync(DataSourceLoadOptionsCustomized options);
Task<BaseComponentResponseDto> BatchUpdateAsync(List<DXBatchUpdate> changes);
Task<StaticDataTypeViewDto?> GetNameByCodeAsync(string code);
}
public class StaticDataTypeAppService : BaseComponentAppService, IStaticDataTypeAppService
{
private readonly IStaticDataTypeRepository _staticDataTypeRepository;
private readonly IStaticDataStructureRepository _staticDataStructureRepository;
private readonly IEmployeeRepository _employeeRepository;
private readonly IStringLocalizer<BaseComponentResource> _baseLocalizer;
public StaticDataTypeAppService(
IStaticDataTypeRepository staticDataTypeRepository,
IStaticDataStructureRepository staticDataStructureRepository,
IEmployeeRepository employeeRepository,
IStringLocalizer<BaseComponentResource> baselocalizer
)
{
_staticDataTypeRepository = staticDataTypeRepository;
_staticDataStructureRepository = staticDataStructureRepository;
_employeeRepository = employeeRepository;
_baseLocalizer = baselocalizer;
}
...
}
I use this module in AMLReportService as following:
public interface ICustomStaticDataTypeAppService : IStaticDataTypeAppService
{
}
public class CustomStaticDataTypeAppService : StaticDataTypeAppService, ICustomStaticDataTypeAppService
{
public CustomStaticDataTypeAppService(IStaticDataTypeRepository staticDataTypeRepository, IStaticDataStructureRepository staticDataStructureRepository, IEmployeeRepository employeeRepository, IStringLocalizer<BaseComponentResource> baselocalizer) : base(staticDataTypeRepository, staticDataStructureRepository, employeeRepository, baselocalizer)
{
}
}
The purpose that I have to create Custom AppService for IStaticDataTypeAppService is: this module will be used in multiple services, so all of its APIs need to be renamed.
In AMLReportService swagger, I can see custom APs and work fine:

Static C# proxy is also generated normally as following:
[Dependency(ReplaceServices = true)]
[ExposeServices(typeof(ICustomStaticDataTypeAppService), typeof(CustomStaticDataTypeClientProxy))]
public partial class CustomStaticDataTypeClientProxy : ClientProxyBase<ICustomStaticDataTypeAppService>, ICustomStaticDataTypeAppService
{
public virtual async Task<LoadResult> GetListAsync(DataSourceLoadOptionsCustomized options)
{
return await RequestAsync<LoadResult>(nameof(GetListAsync), new ClientProxyRequestTypeValue
{
{ typeof(DataSourceLoadOptionsCustomized), options }
});
}
public virtual async Task<BaseComponentResponseDto> BatchUpdateAsync(List<DXBatchUpdate> changes)
{
return await RequestAsync<BaseComponentResponseDto>(nameof(BatchUpdateAsync), new ClientProxyRequestTypeValue
{
{ typeof(List<DXBatchUpdate>), changes }
});
}
public virtual async Task<StaticDataTypeViewDto> GetNameByCodeAsync(string code)
{
return await RequestAsync<StaticDataTypeViewDto>(nameof(GetNameByCodeAsync), new ClientProxyRequestTypeValue
{
{ typeof(string), code }
});
}
}
But in client side, in ServiceProxyScript, I can't find those APIs, I don't know why it's not generated (Only APIs in BaseComponent module are not generated, other APIs in AMLReportService are generated normally). Please help.
Hi,
I get this error when running a background worker after upgrading from v7.3.0 to v9.1.0: The data you have submitted has already been changed by another user. Discard your changes and try again
I found so many questions the same as mine but could not find any answer.
at Volo.Abp.IdentityModel.IdentityModelAuthenticationService.GetAccessTokenAsync(IdentityClientConfiguration configuration)
at Volo.Abp.IdentityModel.IdentityModelAuthenticationService.GetAccessTokenOrNullAsync(String identityClientName)
at Volo.Abp.IdentityModel.IdentityModelAuthenticationService.TryAuthenticateAsync(HttpClient client, String identityClientName)
at Volo.Abp.Http.Client.IdentityModel.IdentityModelRemoteServiceHttpClientAuthenticator.Authenticate(RemoteServiceHttpClientAuthenticateContext context)
at Volo.Abp.Http.Client.IdentityModel.Web.HttpContextIdentityModelRemoteServiceHttpClientAuthenticator.Authenticate(RemoteServiceHttpClientAuthenticateContext context)
at Volo.Abp.Http.Client.ClientProxying.ClientProxyBase`1.RequestAsync(ClientProxyRequestContext requestContext)
at Volo.Abp.Http.Client.ClientProxying.ClientProxyBase`1.RequestAsync[T](ClientProxyRequestContext requestContext)
at Volo.Abp.Http.Client.ClientProxying.ClientProxyBase`1.RequestAsync[T](String methodName, ClientProxyRequestTypeValue arguments)
at MZH.MHIBS.JFSAService.TransactionReports.TransactionReportClientProxy.GenerateReportFileAsync() in ...
at MZH.MHIBS.BackgroundWorker.JFSAService.ScanReportFileWorker.SendReportFile() in ...
at MZH.MHIBS.BackgroundWorker.JFSAService.ScanReportFileWorker.Execute(IJobExecutionContext context) in ...
I get this error after upgrade microservice template from v7.3.0 to v9.1.0. Old version still works fine.
[web_80132ba5-d]: [10:43:56 ERR] ---------- RemoteServiceErrorInfo ----------
[web_80132ba5-d]: {
[web_80132ba5-d]: "code": null,
[web_80132ba5-d]: "message": "An error occurred during the ABP remote HTTP request. (The operation didn't complete within the allowed timeout of '00:00:30'.) See the inner exception for details.",
[web_80132ba5-d]: "details": null,
[web_80132ba5-d]: "data": null,
[web_80132ba5-d]: "validationErrors": null
[web_80132ba5-d]: }
[web_80132ba5-d]:
[web_80132ba5-d]: [10:43:56 ERR] An error occurred during the ABP remote HTTP request. (The operation didn't complete within the allowed timeout of '00:00:30'.) See the inner exception for details.
[web_80132ba5-d]: Volo.Abp.Http.Client.AbpRemoteCallException: An error occurred during the ABP remote HTTP request. (The operation didn't complete within the allowed timeout of '00:00:30'.) See the inner exception for details.
[web_80132ba5-d]: ---> Polly.Timeout.TimeoutRejectedException: The operation didn't complete within the allowed timeout of '00:00:30'.
[web_80132ba5-d]: ---> System.Threading.Tasks.TaskCanceledException: The operation was canceled.
[web_80132ba5-d]: ---> System.IO.IOException: Unable to read data from the transport connection: The I/O operation has been aborted because of either a thread exit or an application request..
[web_80132ba5-d]: ---> System.Net.Sockets.SocketException (995): The I/O operation has been aborted because of either a thread exit or an application request.
[web_80132ba5-d]: --- End of inner exception stack trace ---
I tried to increase the timeout like this, but the error still occur.
public override void PreConfigureServices(ServiceConfigurationContext context)
{
PreConfigure<AbpHttpClientBuilderOptions>(options =>
{
options.ProxyClientActions.Add((remoteServiceName, clientBuilder, client) =>
{
client.Timeout = TimeSpan.FromMinutes(60);
});
options.ProxyClientBuildActions.Add((remoteServiceName, clientBuilder) =>
{
clientBuilder.AddPolicyHandler(Policy.TimeoutAsync<HttpResponseMessage>(TimeSpan.FromMinutes(60)));
clientBuilder.AddResilienceHandler("MHIBS", builder =>
{
builder.AddTimeout(TimeSpan.FromMinutes(60));
});
clientBuilder.AddStandardResilienceHandler(opts =>
{
opts.TotalRequestTimeout.Timeout = TimeSpan.FromMinutes(60);
opts.AttemptTimeout.Timeout = TimeSpan.FromMinutes(60);
opts.CircuitBreaker.SamplingDuration = TimeSpan.FromMinutes(120);
});
});
});
}
<PackageReference Include="Microsoft.Extensions.Http.Polly" Version="9.0.3" /> <PackageReference Include="Microsoft.Extensions.Http.Resilience" Version="9.4.0" />
Please help
I've created a report module and need to use this module in multiple microservices. In this module, I have some pages with following controllers
namespace MZH.MHIBS.Report.Controllers
{
[RemoteService(Name = ReportRemoteServiceConsts.RemoteServiceName)]
[Area(ReportRemoteServiceConsts.ModuleName)]
[Route("api/report/report-item")]
public class ReportItemController : ReportController, IReportItemAppService
{
protected IReportItemAppService ReportItemAppService { get; }
public ReportItemController(IReportItemAppService reportItemAppService)
{
ReportItemAppService = reportItemAppService;
}
[HttpPost]
[Route("batch-update")]
public Task BatchUpdateAsync(List<DXBatchUpdate> changes)
{
return ReportItemAppService.BatchUpdateAsync(changes);
}
[HttpGet]
public Task<LoadResult> GetListAsync(DataSourceLoadOptionsCustomized options)
{
return ReportItemAppService.GetListAsync(options);
}
}
}
Each microservice use different database. When using this module, each microservice will have the same tables. But I don't know how to separate the route of controllers for each microservice. Please help.
[HttpPost]
[UnitOfWork(true, System.Data.IsolationLevel.ReadCommitted)]
public async Task<Guid> CollectDataAsync<T>(string reportCode, string formData, string? permission = "") where T : CollectDataBaseDto
{
if (string.IsNullOrEmpty(permission))
{
throw new UserFriendlyException(_localizer["common_msg_DoNotPermission"]);
}
if (!await AuthorizationService.IsGrantedAsync(permission))
{
throw new UserFriendlyException(string.Format(_localizer["common_msg_DoNotPermission_Item"], permission));
}
if (CurrentUser == null)
{
throw new UserFriendlyException(new ArgumentNullException(nameof(CurrentUser)).Message);
}
if (CurrentTenant == null)
{
throw new UserFriendlyException(new ArgumentNullException(nameof(CurrentTenant)).Message);
}
T? paraObject = default(T);
if (!string.IsNullOrEmpty(formData))
{
paraObject = JsonConvert.DeserializeObject<T>(formData);
}
Dictionary<string, object> keyValueParams = new Dictionary<string, object>();
if (paraObject == null)
{
return Guid.Empty;
}
paraObject.ReportCode = reportCode;
if (CurrentUser != null && CurrentUser.Id.HasValue)
{
paraObject.CurrentUserId = CurrentUser.Id.Value;
string department = await GetCurrentDepartmentAsync();
if (!string.IsNullOrEmpty(department))
{
paraObject.Department = department;
}
}
if (CurrentTenant != null && CurrentTenant.Id.HasValue)
{
paraObject.TenantId = CurrentTenant.Id.Value;
}
Logger.LogInformation("Para object: {0}", paraObject.ObjectToString());
var properties = paraObject.GetType().GetProperties();
foreach (var prop in properties)
{
keyValueParams.Add(prop.Name, prop.GetValue(paraObject) ?? string.Empty);
}
using (_dataFilter.Disable<ISoftDelete>())
{
return await _reportDataRepository.CollectDataAsync(reportCode, paraObject.ReportDate, paraObject.Department, keyValueParams);
}
}
CollectDataAsync method in ReportDataRepository
public async Task<Guid> CollectDataAsync(string reportCode, DateTime reportDate, string department, Dictionary<string, object> keyValueParams)
{
if (string.IsNullOrEmpty(department))
return Guid.Empty;
var dbContext = await GetDbContextAsync();
await SetCommandTimeout(dbContext);
ReportType? reportType = await dbContext.ReportTypes.FirstOrDefaultAsync(a => a.ReportCode == reportCode && !a.IsDeleted);
if (reportType == null)
return Guid.Empty;
ReportStructure? reportStructure = await dbContext.ReportStructures.FirstOrDefaultAsync(a => a.ReportTypeId == reportType.Id && a.IsActive && !a.IsDeleted);
if (reportStructure == null)
return Guid.Empty;
ReportPeriod? reportPeriod = await dbContext.ReportPeriods.FirstOrDefaultAsync(a => a.ReportTypeId == reportType.Id && a.ReportDate.Date == reportDate.Date && !a.IsDeleted);
if (reportPeriod == null)
{
//create new report period for this report type
reportPeriod = new ReportPeriod(Guid.NewGuid());
reportPeriod.ReportTypeId = reportType.Id;
reportPeriod.ReportStructureId = reportStructure.Id;
reportPeriod.ReportDate = reportDate;
await dbContext.ReportPeriods.AddAsync(reportPeriod);
await dbContext.SaveChangesAsync();
}
if (await dbContext.ReportData.AnyAsync(a => a.ReportPeriodId == reportPeriod.Id && a.Department.ToLower() == department.ToLower()))
return reportPeriod.Id;
//Find all datasources and execute scripts
var datasources = dbContext.ReportDataSources.Where(a => a.ReportStructureId == reportStructure.Id && a.Department.Contains(department) && !a.IsDeleted
).OrderBy(a => a.RunningOrder);
foreach (var ds in datasources)
{
await ExecuteSQLCommand(ds, keyValueParams);
}
return reportPeriod.Id;
}
I check ReportPeriod table with these conditions: ReportPeriod? reportPeriod = await dbContext.ReportPeriods.FirstOrDefaultAsync(a => a.ReportTypeId == reportType.Id && a.ReportDate.Date == reportDate.Date && !a.IsDeleted); and expect that it can not have > 1 record with the same ReportType, ReportDate and not deleted.
But when I quickly click Collect Data button on UI 4 times. It still creates 4 records in database.
Where am I wrong?
ABP Framework version: v7.3.0
UI Type: MVC
Database System: EF Core (SQL Server)
Tiered (for MVC) or Auth Server Separated (for Angular): yes
Exception message and full stack trace:
Steps to reproduce the issue: I tried to increase HttpClient timeout but failed. The long running task is in ReportModule which is used in AMLReportService, so I tried to increase HttpClient like this:
[Dependency(ReplaceServices = true)]
[ExposeServices(typeof(OpenLdapManager), typeof(ILdapManager), typeof(LdapManager), typeof(MZHOpenLdapManager))]
public class MZHOpenLdapManager : OpenLdapManager
{
public MZHOpenLdapManager(ILdapSettingProvider ldapSettingProvider) : base(ldapSettingProvider)
{
}
public override async Task<bool> AuthenticateAsync(string username, string password)
{
using (var conn = await CreateLdapConnectionAsync())
{
try
{
Logger.LogInformation("Login with admin account.");
await AuthenticateLdapConnectionAsync(conn, await NormalizeUserNameAsync(await LdapSettingProvider.GetUserNameAsync()), await LdapSettingProvider.GetPasswordAsync());
Logger.LogInformation("Search username");
//conn.SetOption(LdapForNet.Native.Native.LdapOption.LDAP_OPT_REFERRALS, "ignore");
SearchRequest request = new SearchRequest(await GetBaseDnAsync(), await GetUserFilterAsync(username), LdapForNet.Native.Native.LdapSearchScope.LDAP_SCOPE_SUBTREE);
request.SizeLimit = 1;
SearchOptionsControl SuppressReferrals = new SearchOptionsControl(SearchOption.DomainScope);
request.Controls.Add(SuppressReferrals);
//var searchResults = await conn.SearchAsync(await GetBaseDnAsync(), await GetUserFilterAsync(username));
SearchResponse response = conn.SendRequest(request) as SearchResponse;
Logger.LogInformation("Get first item searched");
var userEntry = response.Entries.First();
Logger.LogInformation("Login with username");
await AuthenticateLdapConnectionAsync(conn, userEntry.Dn, password);
Logger.LogInformation("Login LDAP done");
return true;
}
catch (Exception e)
{
Logger.LogException(e);
}
return false;
}
}
protected override async Task<string> NormalizeUserNameAsync(string userName)
{
return $"cn={userName},{await LdapSettingProvider.GetBaseDcAsync()}";
}
protected override Task<string> GetUserFilterAsync(string userName)
{
return Task.FromResult($"(&(objectClass=user)(sAMAccountName={userName}))");
}
protected override Task<string> GetBaseDnAsync()
{
return LdapSettingProvider.GetDomainAsync();
}
protected override Task<string> GetUserEmailAsync(LdapEntry ldapEntry)
{
Logger.LogInformation("Try to get email infor - start");
string email = ldapEntry.ToDirectoryEntry().GetAttribute("mail")?.GetValue<string>();
if (string.IsNullOrWhiteSpace(email))
email = ldapEntry.ToDirectoryEntry().GetAttribute("userPrincipalName")?.GetValue<string>();
Logger.LogInformation("Try to get email infor - end");
return Task.FromResult(email);
}
Login with LDAP successfully, but very slowly. I check logs see below:
In the red area, it takes 10 second after LDAP login done and continue get email infor. Could you pls show me what Abp was doing during that time?