ErrorDescription: RequiresTwoFactor
Hi
Can you try to disable the 2FA of your account?https://account.abp.io/
That does allow the cli to login, but makes our account less secure. It was working with 2FA until recently.
I have tried:
Looks like the following:
public class BillingGroupRepository : CabMDRepository<BillingGroup, BillingGroup_Id>, IBillingGroupRepository
{
#region Constants
private const string Cmd_BillingGroups_Load = "Billing.BillingGroup_Load_1 ";
private const string BillingGroupCacheKey = "BillingGroups";
#endregion Constants
#region Static Force Version Check Method
/// <summary>
/// Force the next load of the entities to do a version check
/// (In this case, just force a reload)
/// </summary>
/// <param name="cache"></param>
internal static void ForceVersionCheck(IDistributedCache<CacheItem> cache)
{
try
{
_semaphore.Wait();
cache.Remove(BillingGroupCacheKey);
}
finally
{
_semaphore.Release();
}
}
/// <summary>
/// Force the next load of the entities to do a version check
/// (In this case, just force a reload)
/// </summary>
/// <param name="cache"></param>
internal async static Task ForceVersionCheckAsync(IDistributedCache<CacheItem> cache)
{
try
{
await _semaphore.WaitAsync().ConfigureAwait(false);
await cache.RemoveAsync(BillingGroupCacheKey).ConfigureAwait(false);
}
finally
{
_semaphore.Release();
}
}
#endregion Static Force Version Check Method
#region Constructors
public BillingGroupRepository(IAsyncQueryableExecuter asyncExecuter,
ICurrentUser currentUser,
IConfiguration configuration,
IDistributedCache<CacheItem> cache = null)
: base(asyncExecuter, currentUser, configuration, cache) // these go in the regular distributed cache (see ForceVersionCheck above)
{
}
#endregion Constructors
public override string EntityTypeName => "Billing Group";
internal override string CacheKeyPrefix => BillingGroupCacheKey;
#region Override Caching
protected override bool IdIsDefault(BillingGroup_Id Id)
{
return Id.Value == default;
}
protected override void DeleteEntityFromDb(BillingGroup_Id Id)
{
throw new NotSupportedException($"Write operations not enabled for {EntityTypeName}s");
}
protected override Task DeleteEntityFromDbAsync(BillingGroup_Id Id, CancellationToken ct = default)
{
throw new NotSupportedException($"Write operations not enabled for {EntityTypeName}s");
}
protected override VersionAndDelete LoadCurrentVersionFromDb()
{
return new VersionAndDelete { Version = 0, LastDeletion = DateTime.MinValue };
}
protected override DbLoadObj<BillingGroup_Id, BillingGroup> LoadEntitiesFromDb(int fromVersion)
{
return Db_LoadBillingGroups(fromVersion);
}
protected override Task<VersionAndDelete> LoadCurrentVersionFromDbAsync(CancellationToken ct = default)
{
return Task.FromResult(new VersionAndDelete { Version = 0, LastDeletion = DateTime.MinValue });
}
protected async override Task<DbLoadObj<BillingGroup_Id, BillingGroup>> LoadEntitiesFromDbAsync(int fromVersion, CancellationToken ct = default)
{
return await Db_LoadBillingGroupsAsync(fromVersion, ct).ConfigureAwait(false);
}
protected override void SaveEntity(BillingGroup entity)
{
throw new NotSupportedException($"Write operations not enabled for {EntityTypeName}s");
}
protected override Task SaveEntityAsync(BillingGroup entity, CancellationToken ct = default)
{
throw new NotSupportedException($"Write operations not enabled for {EntityTypeName}s");
}
protected override DbSaveObj<BillingGroup> SaveEntityToDb(BillingGroup entity, int currentKnownVersion)
{
throw new NotSupportedException($"Write operations not enabled for {EntityTypeName}s");
}
protected override Task<DbSaveObj<BillingGroup>> SaveEntityToDbAsync(BillingGroup entity, int currentKnownVersion, CancellationToken ct)
{
throw new NotSupportedException($"Write operations not enabled for {EntityTypeName}s");
}
#endregion Override Caching
#region DBAccess
private DbLoadObj<BillingGroup_Id, BillingGroup> Db_LoadBillingGroups(int FromVersion)
{
using IDatabase db = new Database(connectionString);
db.AddParameter(new dbParameter("For15", true));
DataSet ds = null;
try
{
Database.ExcuteWithRetry(() => ds = db.Execute<DataSet>(Cmd_BillingGroups_Load), RetryLogger);
return new DbLoadObj<BillingGroup_Id, BillingGroup>
{
List = Db_GetBillingGroupsFromDataSet(ds),
Version = 0,
AllLoaded = true
};
}
finally
{
if (ds != null) ds.Dispose();
}
}
private async Task<DbLoadObj<BillingGroup_Id, BillingGroup>> Db_LoadBillingGroupsAsync(int FromVersion, CancellationToken ct = default)
{
ct.ThrowIfCancellationRequested();
using IDatabase db = new Database(connectionString);
db.AddParameter(new dbParameter("For15", true));
DataSet ds = null;
try
{
await Database.ExcuteWithRetryAsync(async () => ds = await db.ExecuteAsync<DataSet>(Cmd_BillingGroups_Load, ct).ConfigureAwait(false), RetryLogger, ct).ConfigureAwait(false);
return new DbLoadObj<BillingGroup_Id, BillingGroup>
{
List = Db_GetBillingGroupsFromDataSet(ds),
Version = 0,
AllLoaded = true
};
}
finally
{
if (ds != null) ds.Dispose();
}
}
private static ConcurrentDictionary<BillingGroup_Id, BillingGroup> Db_GetBillingGroupsFromDataSet(DataSet ds)
{
// the main list
var retValue = DataTableConcurrentDictionary.CreateDictionary<BillingGroup_Id, BillingGroup>(ds.Tables[0], "ID");
// get additional values
var dvDetail = new DataView(ds.Tables[1], null, "BillingGroupID", DataViewRowState.CurrentRows);
var dvCharge = new DataView(ds.Tables[2], null, "BillingGroupID", DataViewRowState.CurrentRows);
var dvTax = new DataView(ds.Tables[3], null, "BillingGroupID", DataViewRowState.CurrentRows);
// loop through each ba to get associated values
foreach (var bg in retValue.Values)
{
// details
var rows = dvDetail.FindRows(bg.Id).Select(r => r.Row);
bg.Detail = rows.Any() ? DataTableList.CreateList<BillingGroupDetail>(rows).FirstOrDefault() : null;
// charges
rows = dvCharge.FindRows(bg.Id).Select(r => r.Row);
bg.Charge = rows.Any() ? DataTableList.CreateList<BillingGroupCharge>(rows).FirstOrDefault() : null;
// taxes
rows = dvTax.FindRows(bg.Id).Select(r => r.Row);
bg.Tax = rows.Any() ? DataTableList.CreateList<BillingGroupTax>(rows).FirstOrDefault() : null;
}
return retValue;
}
#endregion DBAccess
}
The base CabMDRepository takes IConfiguration so that it can do this:
connectionString = configuration.GetConnectionString("Default");
And so in the set of tests that I want to run that check these ado/sp loading methods, I want to specify the connection string so that we can actually test.
Here's the related code:
Domain
[DependsOn(
typeof(CabMDDomainSharedModule),
typeof(AbpAuditLoggingDomainModule),
typeof(AbpBackgroundJobsDomainModule),
typeof(AbpFeatureManagementDomainModule),
typeof(AbpIdentityProDomainModule),
typeof(AbpPermissionManagementDomainIdentityModule),
typeof(AbpIdentityServerDomainModule),
typeof(AbpPermissionManagementDomainIdentityServerModule),
typeof(AbpSettingManagementDomainModule),
typeof(SaasDomainModule),
typeof(TextTemplateManagementDomainModule),
typeof(LeptonThemeManagementDomainModule),
typeof(LanguageManagementDomainModule),
typeof(VoloAbpCommercialSuiteTemplatesModule),
typeof(AbpEmailingModule),
typeof(BlobStoringDatabaseDomainModule),
typeof(AbpPaymentDomainModule),
typeof(AbpPaymentPayuDomainModule),
typeof(AbpPaymentTwoCheckoutDomainModule),
typeof(FileManagementDomainModule),
typeof(DocsDomainModule),
typeof(CmsKitProDomainModule)
)]
public class CabMDDomainModule : AbpModule
{
// lots of code
}
EfCore (Unified)
[DependsOn(
typeof(CabMDDomainModule),
typeof(AbpIdentityProEntityFrameworkCoreModule),
typeof(AbpIdentityServerEntityFrameworkCoreModule),
typeof(AbpPermissionManagementEntityFrameworkCoreModule),
typeof(AbpSettingManagementEntityFrameworkCoreModule),
typeof(AbpEntityFrameworkCoreSqlServerModule),
typeof(AbpBackgroundJobsEntityFrameworkCoreModule),
typeof(AbpAuditLoggingEntityFrameworkCoreModule),
typeof(AbpFeatureManagementEntityFrameworkCoreModule),
typeof(LanguageManagementEntityFrameworkCoreModule),
typeof(SaasEntityFrameworkCoreModule),
typeof(TextTemplateManagementEntityFrameworkCoreModule),
typeof(BlobStoringDatabaseEntityFrameworkCoreModule),
typeof(AbpPaymentEntityFrameworkCoreModule),
typeof(FileManagementEntityFrameworkCoreModule),
typeof(DocsEntityFrameworkCoreModule),
typeof(CmsKitProEntityFrameworkCoreModule)
)]
public class CabMDEntityFrameworkCoreModule : AbpModule
{
// lots of code
}
Unified DbContext:
protected override void OnModelCreating(ModelBuilder builder)
{
base.OnModelCreating(builder);
/* Include modules to your migration db context */
builder.ConfigurePermissionManagement();
builder.ConfigureSettingManagement();
builder.ConfigureBackgroundJobs();
builder.ConfigureAuditLogging();
builder.ConfigureIdentityPro();
builder.ConfigureIdentityServer();
builder.ConfigureFeatureManagement();
builder.ConfigureLanguageManagement();
builder.ConfigureSaas();
builder.ConfigureTextTemplateManagement();
builder.ConfigureCmsKitPro();
builder.ConfigureBlobStoring();
builder.ConfigurePayment();
builder.ConfigureFileManagement();
builder.ConfigureDocs();
/* Configure the shared tables (with included modules) here */
//builder.Entity<IdentityUser>(b =>
//{
// b.ToTable(AbpIdentityDbProperties.DbTablePrefix + "Users"); //Sharing the same table "AbpUsers" with the IdentityUser
// b.ConfigureByConvention();
// b.ConfigureAbpUser();
// /* Configure mappings for your additional properties.
// * Also see the CabMDEfCoreEntityExtensionMappings class.
// */
//});
/* Configure your own tables/entities inside the ConfigureCabMD method */
builder.ConfigureCabMD();
}
I'll note also that the abp suite refuses to bring up modules and the abp add-module command says it's failing but provides no information. Is there a way to get more verbose information from the CLI there?
All of those things are in place, and I've done them again with no success. Also, when I go to add a migration - it's empty and I can see that it's because the blob related tables are in from when I added CmsKitPro.
Can I email you? Since I can't produce this on a new project.
So, I've been trying to re-create the issue but I haven't yet found a set of steps useful.
I'm wondering if it's due to us adding the CmsPro kit module before this. Is there an order that is important?
dotnet build runs without error as well, so the abp add-module failure I can't figure out why it's failing. There's no verbose mode for the add-module command.
After installing CMS pro (v4.3.3) module, I'm getting the following from the API host:
[13:53:10 INF] Request starting HTTP/2 GET https://localhost:44392/swagger/v1/swagger.json - -
[13:53:10 ERR] An unhandled exception has occurred while executing the request.
Swashbuckle.AspNetCore.SwaggerGen.SwaggerGeneratorException: Failed to generate Operation for action - Volo.CmsKit.Admin.Comments.CommentAdminController.GetListAsync (Volo.CmsKit.Admin.HttpApi). See inner exception
---> Swashbuckle.AspNetCore.SwaggerGen.SwaggerGeneratorException: Failed to generate schema for type - Volo.Abp.Application.Dtos.PagedResultDto`1[Volo.CmsKit.Admin.Comments.CommentWithAuthorDto]. See inner exception
---> System.InvalidOperationException: Can't use schemaId "$CmsUserDto" for type "$Volo.CmsKit.Admin.Comments.CmsUserDto". The same schemaId is already used for type "$Volo.CmsKit.Users.CmsUserDto"
at Swashbuckle.AspNetCore.SwaggerGen.SchemaRepository.RegisterType(Type type, String schemaId)
at Swashbuckle.AspNetCore.SwaggerGen.SchemaGenerator.GenerateReferencedSchema(DataContract dataContract, SchemaRepository schemaRepository, Func`1 definitionFactory)
at Swashbuckle.AspNetCore.SwaggerGen.SchemaGenerator.GenerateConcreteSchema(DataContract dataContract, SchemaRepository schemaRepository)
at Swashbuckle.AspNetCore.SwaggerGen.SchemaGenerator.GenerateSchemaForMember(MemberInfo memberInfo, SchemaRepository schemaRepository, DataProperty dataProperty)
at Swashbuckle.AspNetCore.SwaggerGen.SchemaGenerator.CreateObjectSchema(DataContract dataContract, SchemaRepository schemaRepository)
at Swashbuckle.AspNetCore.SwaggerGen.SchemaGenerator.GenerateReferencedSchema(DataContract dataContract, SchemaRepository schemaRepository, Func`1 definitionFactory)
at Swashbuckle.AspNetCore.SwaggerGen.SchemaGenerator.GenerateConcreteSchema(DataContract dataContract, SchemaRepository schemaRepository)
at Swashbuckle.AspNetCore.SwaggerGen.SchemaGenerator.GenerateSchemaForType(Type type, SchemaRepository schemaRepository)
at Swashbuckle.AspNetCore.SwaggerGen.SchemaGenerator.CreateArraySchema(DataContract dataContract, SchemaRepository schemaRepository)
at Swashbuckle.AspNetCore.SwaggerGen.SchemaGenerator.GenerateConcreteSchema(DataContract dataContract, SchemaRepository schemaRepository)
at Swashbuckle.AspNetCore.SwaggerGen.SchemaGenerator.GenerateSchemaForMember(MemberInfo memberInfo, SchemaRepository schemaRepository, DataProperty dataProperty)
at Swashbuckle.AspNetCore.SwaggerGen.SchemaGenerator.CreateObjectSchema(DataContract dataContract, SchemaRepository schemaRepository)
at Swashbuckle.AspNetCore.SwaggerGen.SchemaGenerator.GenerateReferencedSchema(DataContract dataContract, SchemaRepository schemaRepository, Func`1 definitionFactory)
at Swashbuckle.AspNetCore.SwaggerGen.SchemaGenerator.GenerateConcreteSchema(DataContract dataContract, SchemaRepository schemaRepository)
at Swashbuckle.AspNetCore.SwaggerGen.SchemaGenerator.GenerateSchemaForType(Type type, SchemaRepository schemaRepository)
at Swashbuckle.AspNetCore.SwaggerGen.SwaggerGenerator.GenerateSchema(Type type, SchemaRepository schemaRepository, PropertyInfo propertyInfo, ParameterInfo parameterInfo)
--- End of inner exception stack trace ---
at Swashbuckle.AspNetCore.SwaggerGen.SwaggerGenerator.GenerateSchema(Type type, SchemaRepository schemaRepository, PropertyInfo propertyInfo, ParameterInfo parameterInfo)
at Swashbuckle.AspNetCore.SwaggerGen.SwaggerGenerator.CreateResponseMediaType(ModelMetadata modelMetadata, SchemaRepository schemaRespository)
at System.Linq.Enumerable.ToDictionary[TSource,TKey,TElement](IEnumerable`1 source, Func`2 keySelector, Func`2 elementSelector, IEqualityComparer`1 comparer)
at System.Linq.Enumerable.ToDictionary[TSource,TKey,TElement](IEnumerable`1 source, Func`2 keySelector, Func`2 elementSelector)
at Swashbuckle.AspNetCore.SwaggerGen.SwaggerGenerator.GenerateResponse(ApiDescription apiDescription, SchemaRepository schemaRepository, String statusCode, ApiResponseType apiResponseType)
at Swashbuckle.AspNetCore.SwaggerGen.SwaggerGenerator.GenerateResponses(ApiDescription apiDescription, SchemaRepository schemaRepository)
at Swashbuckle.AspNetCore.SwaggerGen.SwaggerGenerator.GenerateOperation(ApiDescription apiDescription, SchemaRepository schemaRepository)
--- End of inner exception stack trace ---
at Swashbuckle.AspNetCore.SwaggerGen.SwaggerGenerator.GenerateOperation(ApiDescription apiDescription, SchemaRepository schemaRepository)
at Swashbuckle.AspNetCore.SwaggerGen.SwaggerGenerator.GenerateOperations(IEnumerable`1 apiDescriptions, SchemaRepository schemaRepository)
at Swashbuckle.AspNetCore.SwaggerGen.SwaggerGenerator.GeneratePaths(IEnumerable`1 apiDescriptions, SchemaRepository schemaRepository)
at Swashbuckle.AspNetCore.SwaggerGen.SwaggerGenerator.GetSwagger(String documentName, String host, String basePath)
at Swashbuckle.AspNetCore.Swagger.SwaggerMiddleware.Invoke(HttpContext httpContext, ISwaggerProvider swaggerProvider)
at Microsoft.AspNetCore.Authorization.AuthorizationMiddleware.Invoke(HttpContext context)
at Volo.Abp.AspNetCore.MultiTenancy.MultiTenancyMiddleware.InvokeAsync(HttpContext context, RequestDelegate next)
at Microsoft.AspNetCore.Builder.UseMiddlewareExtensions.<>c__DisplayClass6_1.<<UseMiddlewareInterface>b__1>d.MoveNext()
--- End of stack trace from previous location ---
at Microsoft.AspNetCore.Authentication.AuthenticationMiddleware.Invoke(HttpContext context)
at Microsoft.AspNetCore.Localization.RequestLocalizationMiddleware.Invoke(HttpContext context)
at Microsoft.AspNetCore.RequestLocalization.AbpRequestLocalizationMiddleware.InvokeAsync(HttpContext context, RequestDelegate next)
at Microsoft.AspNetCore.Builder.UseMiddlewareExtensions.<>c__DisplayClass6_1.<<UseMiddlewareInterface>b__1>d.MoveNext()
--- End of stack trace from previous location ---
at Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware.Invoke(HttpContext context)
Perfect, that seems to have done the trick and also (bonus) not required me to split interfaces! Thanks!
So, first - we're using the concept of strongly typed id's from here:
So a simple class of ours might look like:
public class GeneratedReport : GeneratedReportBase<GeneratedReportStatus, GeneratedReportFileType>, IEntity<GeneratedReport_Id>
{
public GeneratedReport_Id Id { get; set; }
public int SortOrder { get; set; }
public object[] GetKeys()
{
return new object[] { Id };
}
}
where GeneratedReport_Id
is:
/// <summary>
/// https://andrewlock.net/strongly-typed-id-updates/
/// </summary>
[StronglyTypedId(jsonConverter: StronglyTypedIdJsonConverter.SystemTextJson | StronglyTypedIdJsonConverter.NewtonsoftJson, backingType: StronglyTypedIdBackingType.Guid)]
public partial struct GeneratedReport_Id { }
And that works great. We use tons of guids and in migrating our solution (mix of .net48 with WebForm/Mvc) and we really wanted to enable ourselves to catch errors where Guids were being used incorrectly. To this we had to write a data layer that interacted with the db which was relatively easy to do with the extensibility of abp. The data engineer had everything but the site working (started on abp free, moved to commercial) and now we're at the point that I've created the commercial solution and we're trying to start working on the UI which is primarily Kendo UI based (instead of Datatables). I have a blog post coming about that for the community.
So, on to the method in question. Obviously our app services have interfaces and those interfaces can contain these strongly typed ids:
[HttpPost]
[Route("{id}")]
public Task SaveReportContentAsync(GeneratedReport_Id Id, byte[] ReportContent, CancellationToken cancellationToken = default)
{
// TODO: Copy implementation
throw new NotImplementedException();
}
I see that the api-generation code is looking at GeneratedReport_Id and noticing it's not a primitive type so it breaks the inherent rule but ..
Since SaveReportContentAsync is detailed in the IGeneratedReportAppService interface, it's required when creating the GeneratedReportsController in the HttpApi project. Since I only expect the API methods to be able to get lists of reports and not make them (report content is only created by a background job that uses Telerik Reporting), I don't really want that to be part of the API but I don't see any documentation letting me attribute certain methods to ignore them.