Base solution for your next web application

Activities of "hra"

This is a post to store knowledge for future reference.

We have an ANZ application, and have segregated our application entities into its own DbContext - separate from ANZ. We do "share" some entities, however - for example - the AbpUsers table is included in our own DbContext - but we disable migrations for that table so it does not attempt to generate the table a second time.

We have started writing unit/integration tests for our business logic - and came across this problem - how do we mock the database, when we need BOTH the Abp tables, AND our own table schemas to be created into the mocked sqlite dbcontext? If we do nothing, we get an error when running the test, that tables could not be found.

The answer required 2 adjustments to achieve.

  1. Edit 'XXX.Test.Base/DependencyInjection/ServiceCollectionRegistrar.cs' to publish the sqlite in-memory connection to IoC
public static class ServiceCollectionRegistrar
{
    public static void Register(IIocManager iocManager)
    {
        RegisterIdentity(iocManager);

        var builder = new DbContextOptionsBuilder<AnzDbContext>();

        var inMemorySqlite = new SqliteConnection("Data Source=:memory:");
        builder.UseSqlite(inMemorySqlite);

        // register connection for reuse
        iocManager.IocContainer.Register(Component.For<SqliteConnection>().Instance(inMemorySqlite));

        iocManager.IocContainer.Register(
            Component
                .For<DbContextOptions<AnzDbContext>>()
                .Instance(builder.Options)
                .LifestyleSingleton()
        );

        inMemorySqlite.Open();
        
        new AnzDbContext(builder.Options).Database.EnsureCreated();
    }

    private static void RegisterIdentity(IIocManager iocManager)
    {
        var services = new ServiceCollection();

        IdentityRegistrar.Register(services);

        WindsorRegistrationHelper.CreateServiceProvider(iocManager.IocContainer, services);
    }
}
  1. We derive our unit tests from the same unit test base as our Abp unit test classes - that triggers the Abp tables to be generated/mocked
  2. We edit our custom unit test "ServiceCollectionRegistrar" to locate the sqlite database connection from IoC, rather than initialize a new DB - however, we also need to use reflection to dig into EF to FORCE our custom table schema to be generated. Usually, the code will call EnsureCreated( new CustomDbContext(builder.Options).Database.EnsureCreated();) - but reading the XmlDoc, you will see that if the DB already exists, it will do nothing - not even generate the schema - so we need to use reflection to access the Create() and CreateTables() methods.
public static void Register(IIocManager iocManager)
{
    RegisterIdentity(iocManager);

    var builder = new DbContextOptionsBuilder<CustomDbContext>();

    var inMemorySqlite = iocManager.Resolve<SqliteConnection>();
    builder.UseSqlite(inMemorySqlite);

    iocManager.IocContainer.Register(
        Component
            .For<DbContextOptions<CustomDbContext>>()
            .Instance(builder.Options)
            .LifestyleSingleton()
    );

    inMemorySqlite.Open();

    var db = new CustomDbContext(builder.Options);

    // FORCE the table schema to be created - even though the database already exists, it only contains ANZ tables - none of our own
    var propDependencies = typeof(DatabaseFacade).GetProperty("Dependencies", System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic);
    var dependencies = (IDatabaseFacadeDependencies)propDependencies.GetValue(db.Database);
    if (dependencies.DatabaseCreator is IRelationalDatabaseCreator databaseCreator)
    {
        databaseCreator.Create();
        databaseCreator.CreateTables();
    }
}

Hi,

AspNetZero Angular+Core: API: v10.0.0 | Client: v10.0.0 (20220815)

I have scanned the forum, and found older discussions relating to this error, such as: https://support.aspnetzero.com/QA/Questions/4162/transaction-is-not-associated-with-the-current-connection

The solutions proposed do not work for me.

My scenario is that I have the ANZ dbcontext, and I also have a second DbContext. It works at regular runtime, the above error only happens during unit testing.

I have this in both my DbContexts:

Configuration.ReplaceService<IEfCoreTransactionStrategy, DbContextEfCoreTransactionStrategy>(DependencyLifeStyle.Transient);

And the error occurs when testing an AppService which attempts to resolve my custom DbContext

        IDbContextProvider<CustomDbContext> _customDbContextProvider
        
        public CustomDbContext DbContext => _customDbContextProvider.GetDbContext(); // error happens here

Yes, turning off transactions fixes the issue - however, my code needs to be transactional.

Thanks,

I am doing a review of nuget packages introduced by my own code changes to ANZ. During this review, I have encountered at least one deprecated 3rd party dependency in the base ANZ/ABP.

Example: Microsoft.Extensions.Configuration.AzureKeyVault

Is Volosoft aware of this deprecated package (and any others), and what is the upgrade roadmap?

While (in this case) Microsoft has commented this particular dependency is still supported for the time being - it's deprecation was announced over a year ago, I just wish to know what the Volosofts timelines are, so I can time my own upgrade of the ANZ template to be after this happens.

Thank you

I have a service which is consumed by both my web request logic, and my background job - yes it maintains some state during the operation.

I was using "IPerWebRequestDependency" scope - which is fine for the web request - but in my background worker, every dependent of this service gets its own (transient) instance.

How do I register this service to behave the same for both?

Thanks,

AspNetZero 10.0.0

I have paired down my backgroundjob to something very, very simple - but most of the time, it goes straight to "Abandoned" and never executes.

I have found the only way I can get one to execute is to:

  1. delete all abandoned background jobs
  2. refresh my browser which is calling the Application Service to enqueue the job
  3. then call the application service to enqueue the job

Then what happens is, it will queue a single job - but if I click the button to queue a second one, it goes into abandoned. Why is this? Why is there no logging when it chooses to abandon a job? I have also registered on the bus for error callbacks - and none come through.

Thanks,

AspNetZero 10.0.0

The XmlDoc for UserManager.CreateAsync states:

    //     Is this a static role? Static roles can not be deleted, can not change their
    //     name. They can be used programmatically.
    public virtual bool IsStatic { get; set; }

However, the user interface does not prevent the editing of the name

Note: I was able to save these changes

I cannot find any documentation detailing "User Account Lockout".

On the User creation page, there is a tickbox called "Lockout Enabled" - what does this do?

I have had user accounts get locked out. Where do I see in the interface whether a user account is locked out or not?

For the accounts that had become "locked out", I went to the users list, clicked "Actions" and chose "Unlock" - is that what is supposed to be done? How do we know that the unlock worked (other than asking the user if they were able to log in...)

My Assumptions:

The "Lockout enabled" checkbox does not mean the user account is locked out, it means it CAN be locked out if they violate login policies (failed attempts). If this is not ticked, they can fail login as many times as they like, they will not be locked out. There is NO UI for seeing if a user account is currently locked out or not.

Prerequisites

AspNetZero 10

We are using Microsoft Authentication, and have noticed that after a user has provisioned their account using "Sign in with Microsoft", their name appears as: "John Smith Smith" - because their first and last names are squeezed into the AbpUser.FirstName in addition to their last name being placed in Abpuser.Surname.

After recently being given a code sample, we can now see why: in MicrosoftAuthProviderApi is the following code:

            var result = new ExternalAuthUserInfo
            {
                Name = MicrosoftAccountHelper.GetDisplayName(payload),
                EmailAddress = MicrosoftAccountHelper.GetEmail(payload),
                Surname = MicrosoftAccountHelper.GetSurname(payload),
                Provider = Name,
                ProviderKey = MicrosoftAccountHelper.GetId(payload)
            };

I believe It should be

            var result = new ExternalAuthUserInfo
            {
                Name = MicrosoftAccountHelper.GetGivenName(payload),
                EmailAddress = MicrosoftAccountHelper.GetEmail(payload),
                Surname = MicrosoftAccountHelper.GetSurname(payload),
                Provider = Name,
                ProviderKey = MicrosoftAccountHelper.GetId(payload)
            };

Prerequisites

  • AspNetZero 10

We have enabled Microsoft authentication, and have had users report that they get an error while attempting to associate their Microsoft account with our product.

We tracked it back to the fact that some of the users do not have a "Last Name" set in their Microsoft accounts.

This is the error in AspNetZero. You can clearly see that AspNetZero demands a last name, which is inherently incompatible with Microsoft accounts.

RROR 2021-10-19 03:01:25,045 [6    ] Mvc.ExceptionHandling.AbpExceptionFilter - An error occurred while updating the entries. See the inner exception for details.
Microsoft.EntityFrameworkCore.DbUpdateException: An error occurred while updating the entries. See the inner exception for details.
 ---> Microsoft.Data.SqlClient.SqlException (0x80131904): Cannot insert the value NULL into column 'Surname', table 'cloudrisk-staging.dbo.AbpUsers'; column does not allow nulls. INSERT fails.
The statement has been terminated.
   at Microsoft.Data.SqlClient.SqlCommand.<>c.<ExecuteDbDataReaderAsync>b__169_0(Task`1 result)
   at System.Threading.Tasks.ContinuationResultTaskFromResultTask`2.InnerInvoke()
   at System.Threading.Tasks.Task.<>c.<.cctor>b__277_0(Object obj)
   at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state)
--- End of stack trace from previous location ---
   at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state)
   at System.Threading.Tasks.Task.ExecuteWithThreadLocal(Task& currentTaskSlot, Thread threadPoolThread)
--- End of stack trace from previous location ---
   at Microsoft.EntityFrameworkCore.Storage.RelationalCommand.ExecuteReaderAsync(RelationalCommandParameterObject parameterObject, CancellationToken cancellationToken)
   at Microsoft.EntityFrameworkCore.Storage.RelationalCommand.ExecuteReaderAsync(RelationalCommandParameterObject parameterObject, CancellationToken cancellationToken)
   at Microsoft.EntityFrameworkCore.Update.ReaderModificationCommandBatch.ExecuteAsync(IRelationalConnection connection, CancellationToken cancellationToken)
ClientConnectionId:a272394f-be75-4a1d-ab5b-4f6838ec0bdd
Error Number:515,State:2,Class:16
ClientConnectionId before routing:e3fa7b20-81ee-4edd-9aa2-c99dfb447e78
Routing Destination:b8b26ffad02d.tr1.australiacentral1-a.worker.database.windows.net,11020
   --- End of inner exception stack trace ---
   at Microsoft.EntityFrameworkCore.Update.ReaderModificationCommandBatch.ExecuteAsync(IRelationalConnection connection, CancellationToken cancellationToken)
   at Microsoft.EntityFrameworkCore.Update.Internal.BatchExecutor.ExecuteAsync(IEnumerable`1 commandBatches, IRelationalConnection connection, CancellationToken cancellationToken)
   at Microsoft.EntityFrameworkCore.Update.Internal.BatchExecutor.ExecuteAsync(IEnumerable`1 commandBatches, IRelationalConnection connection, CancellationToken cancellationToken)
   at Microsoft.EntityFrameworkCore.Update.Internal.BatchExecutor.ExecuteAsync(IEnumerable`1 commandBatches, IRelationalConnection connection, CancellationToken cancellationToken)
   at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.StateManager.SaveChangesAsync(IList`1 entriesToSave, CancellationToken cancellationToken)
   at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.StateManager.SaveChangesAsync(DbContext _, Boolean acceptAllChangesOnSuccess, CancellationToken cancellationToken)
   at Microsoft.EntityFrameworkCore.SqlServer.Storage.Internal.SqlServerExecutionStrategy.ExecuteAsync[TState,TResult](TState state, Func`4 operation, Func`4 verifySucceeded, CancellationToken cancellationToken)
   at Microsoft.EntityFrameworkCore.DbContext.SaveChangesAsync(Boolean acceptAllChangesOnSuccess, CancellationToken cancellationToken)
   at Microsoft.EntityFrameworkCore.DbContext.SaveChangesAsync(Boolean acceptAllChangesOnSuccess, CancellationToken cancellationToken)
   at Abp.EntityFrameworkCore.AbpDbContext.SaveChangesAsync(CancellationToken cancellationToken)
   at Abp.Zero.EntityFrameworkCore.AbpZeroCommonDbContext`3.SaveChangesAsync(CancellationToken cancellationToken)
   at Abp.EntityFrameworkCore.Uow.EfCoreUnitOfWork.SaveChangesInDbContextAsync(DbContext dbContext)
   at Abp.EntityFrameworkCore.Uow.EfCoreUnitOfWork.SaveChangesAsync()
   at Abp.Authorization.Users.AbpUserStore`2.CreateAsync(TUser user, CancellationToken cancellationToken)
   at Abp.Domain.Uow.UnitOfWorkInterceptor.InternalInterceptAsynchronous[TResult](IInvocation invocation)
   at Microsoft.AspNetCore.Identity.UserManager`1.CreateAsync(TUser user)
   at Abp.Authorization.Users.AbpUserManager`2.CreateAsync(TUser user)
   at Abp.Domain.Uow.UnitOfWorkInterceptor.InternalInterceptAsynchronous[TResult](IInvocation invocation)
   at Microsoft.AspNetCore.Identity.UserManager`1.CreateAsync(TUser user, String password)
   at Abp.Domain.Uow.UnitOfWorkInterceptor.InternalInterceptAsynchronous[TResult](IInvocation invocation)
   at HRA.Portal.Authorization.Users.UserRegistrationManager.RegisterAsync(String name, String surname, String emailAddress, String userName, String plainPassword, Boolean isEmailConfirmed, String emailActivationLink) in /home/vsts/work/1/s/aspnet-core/src/HRA.Portal.Core/Authorization/Users/UserRegistrationManager.cs:line 87
   at HRA.Portal.Web.Controllers.TokenAuthController.RegisterExternalUserAsync(ExternalAuthUserInfo externalLoginInfo) in /home/vsts/work/1/s/aspnet-core/src/HRA.Portal.Web.Core/Controllers/TokenAuthController.cs:line 572
   at HRA.Portal.Web.Controllers.TokenAuthController.ExternalAuthenticate(ExternalAuthenticateModel model) in /home/vsts/work/1/s/aspnet-core/src/HRA.Portal.Web.Core/Controllers/TokenAuthController.cs:line 490
   at lambda_method2047(Closure , Object )
   at Microsoft.AspNetCore.Mvc.Infrastructure.ActionMethodExecutor.AwaitableObjectResultExecutor.Execute(IActionResultTypeMapper mapper, ObjectMethodExecutor executor, Object controller, Object[] arguments)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.&lt;InvokeActionMethodAsync&gt;g__Awaited|12_0(ControllerActionInvoker invoker, ValueTask`1 actionResultValueTask)
   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|25_0(ResourceInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)

I'm setting up Azure A/D authentication integation to our AspNetZero Angular/Core application.

After reading this document: https://docs.microsoft.com/en-us/azure/active-directory/develop/howto-convert-app-to-be-multi-tenant

I found that all I allegedly need to do is enable Microsoft (social) logins - which AspNetZero supports. https://docs.aspnetzero.com/en/aspnet-core-angular/latest/Features-Angular-Social-Logins

I did so, and the "Microsoft" button appears on the tenant login page. Great. However, after using that button to log into my Microsoft account, the AspNetZero page shows an error popup "Could not validate external user login".

I'm guesing there is some step I am missing? But I don't know what it is. Something method I'm supposed to override in the .Net Core code?

The following documentation explains what should happen if an external account is and isn't identified for EXTERNAL authentication - but I'm not sure if that should apply to Microsoft auth as your documentation identifies that as "Social" authentication. https://aspnetboilerplate.com/Pages/Documents/Zero/User-Management

Anyway, the missing knowledge is... I ASSUME that one should only be able to "log in" with a Microsoft account that was used to first CREATE an AspNetZero user account... however, when I visit the tenant "create account" page, there is no "Create with Microsoft" button...

What am I missing?

Thanks,

--- UPDATE --- I've found something in the TokenAuthController ProviderKey comparison which looks likely to be a bug... confirm? Note how the provider key in the UserInfo object differs from the provider key in the Model by only the hypens being stripped (see watch window in screenshot). Bug?

--- UPDATE 2 --- Using the inspector, I updated the ProviderKey to match such that the comparison passes. The next issue is that by the time "UserManager.FindAsync" is called, no tenant id is provided by the code - which means the the query against UserLogin doesnt find the entry that is mapped to a specific tenant. I'm guessing this should have been populated somehow by now? How is the query to find the UserLogin supposed to know which tenant to search under?

--- UPDATE 3 ---

Clearly there are issues here. Status? https://github.com/aspnetzero/aspnet-zero-core/issues/3046

I guess these are the same issues. Broken for almost 2 years? I can get accounts to be created if I tweak the ProviderKey comparison to strip hyphens - however the activate user cannot log in (error "User name XXX is already taken") because the search for the user account is performed with a null tenancyName.

Showing 11 to 20 of 27 entries